Skip to content
This repository has been archived by the owner on Oct 18, 2021. It is now read-only.

Nike-Inc/cerberus-archaius-client

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

71 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Cerberus Archaius Client

OSS Lifecycle

A java based client library that surfaces Cerberus secrets via Archaius.

Archaius is a configuration management library created by Netflix which enables dynamic runtime properties from multiple configuration sources such as URLs, JDBC, and Amazon DynamoDB.

To learn more about Cerberus, please visit the Cerberus website.

Deprecation Notice 10/14/2021

Netflix's Archaius seems to have been completely abandoned with its latest release being in 2019. There are several dozen unresolved issues and pull requests and the wiki hasn't been updated since 2013. This is a potential security concern and it raises concerns with key library incompatibilities.

With that in mind, as of October 2021, support for the Cerberus Archaius Client has been deprecated. This repository will be archived and marked as read-only. You are free to continue using the client if it serves your purposes but no support will be provided. We recommend other available secure Java clients in its place. You can find all available clients here.

Publishing Notice 3/17/2021

As of spring 2021, JFrog has decided to sunset Bintray and JCenter. Due to this decision, we are pausing our open source publishing of the Cerberus Archaius Client. However, we will still be updating the source code and making new GitHub releases.

In order to build the jar yourself, run this command:

./gradlew assemble

The jar will be located in ./build/libs/.

Quickstart

Using the Archaius Provider

There are two ways in which you can use the Archaius provider that comes with the cerberus client.

CerberusConfigurationSource or NamespacedCerberusConfigurationSource

There are two configuration source classes provided by this client. The major difference is how the properties pulled from Cerberus are keyed.

CerberusConfigurationSource

Properties pulled from Cerberus and added to the configuration source will be keyed exactly the same way they are in Cerberus.

For example, if you configure this to pull properties from the a path, such as app/demo/config, each key/value pair at that node will be exposed as-is throughout all of Archaius. If there are no chances of a collision or things are scoped appropriately, this may not be an issue.

NamespacedCerberusConfigurationSource

Properties pulled from Cerberus and added to the configuration source will be keyed using the full path to the property.

For example, if we have property foo and bar at the path, app/demo/config, each will be added to the configuration source with the key of app.demo.config.foo and app.demo.config.bar respectively. This is useful in mitigating the chances for property name collision in Archaius.

The examples below use NamespacedCerberusConfigurationSource

Add the Configuration Source to the ConfigurationManager

Using this pattern, the properties stored in Cerberus will treated as just another set of properties in addition to existing sources like property files. All properties can then be accessed using the DynamicPropertyFactory.getInstance().getXXX methods. This can be achieve with either nonpolling or polling configuration.

Nonpolling configuration (Recommended)

Most applications do not need to poll Cerberus for changes. If secrets change, the instances can simply be restarted or redeployed to pick up new values. The code shown below is a sample implementation of AbstractModule that instantiates a ConcurrentMapConfiguration and adds it to the existing ConfigurationManager.

    public class CerberusArchaiusModule extends AbstractModule {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        private static final String SECRETS_PATH_PROP_NAME = "cerberus.config.path";
        private static final String SECRETS_DEFAULT_CERBERUS_PATH = "app/cerberus-demo/config";
    
        @Override
        protected void configure() {
            /*
             * First let's look up the path for where to read in Cerberus.
             * This examples show us attempting to source it from an Archaius property, but defaulting if not found.
             */
            final String cerberusPath = DynamicPropertyFactory.getInstance()
                    .getStringProperty(SECRETS_PATH_PROP_NAME, SECRETS_DEFAULT_CERBERUS_PATH)
                    .get();
    
            /*
             * So long as we've got a path, let's pull the config into Archaius from Cerberus.
             */
            if (cerberusPath != null && !cerberusPath.isEmpty()) {
                logger.info("Adding Cerberus as Configuration source. Cerberus Path = " + cerberusPath);
    
                /*
                 * Create a configuration source passing in a Cerberus client using the ArchaiusCerberusClientFactory and
                 * the path we looked up above.  This factory will attempt to resolve cerberus configuration detailts,
                 * like the URL and token from Archaius properties.
                 */
                final NamespacedCerberusConfigurationSource namespacedCerberusConfigurationSource = new NamespacedCerberusConfigurationSource(
                        ArchaiusCerberusClientFactory.getClient(), cerberusPath);
                      
                /*
                 * Read secrets from Cerberus.
                 */
                AbstractConfiguration cerberusConfig = null;
                try {
                     cerberusConfig = namespacedCerberusConfigurationSource.getConfig();
                } catch (Exception e) {
                    throw new RuntimeException("Unable to read secrets from Cerberus", e);
                }
    
                final ConcurrentCompositeConfiguration configInstance = (ConcurrentCompositeConfiguration) ConfigurationManager
                        .getConfigInstance();
    
                configInstance.addConfiguration(cerberusConfig);
            } else {
                logger.info("Property corresponding to the Cerberus path for secrets not found! "
                        + "Cerberus not added as Configuration source");
            }
        }
    }
Polling configuration

Polling allows an application to pick up changes dynamically at runtime. Usually this is not needed because simply restarting or redeploying is good enough for most applications. The code shown below is a sample implementation of AbstractModule that instantiates a PolledConfigurationSource and adds it to the existing ConfigurationManager.

    public class CerberusArchaiusModule extends AbstractModule {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        private static final int POLL_INIT_DELAY = 1000;
        private static final int SECRETS_POLL_INTERVAL = (int) TimeUnit.HOURS.toMillis(1);
        private static final String SECRETS_PATH_PROP_NAME = "cerberus.config.path";
        private static final String SECRETS_DEFAULT_CERBERUS_PATH = "app/cerberus-demo/config";
    
        @Override
        protected void configure() {
            /*
             * First let's look up the path for where to read in Cerberus.
             * This examples show us attempting to source it from an Archaius property, but defaulting if not found.
             */
            final String cerberusPath = DynamicPropertyFactory.getInstance()
                    .getStringProperty(SECRETS_PATH_PROP_NAME, SECRETS_DEFAULT_CERBERUS_PATH)
                    .get();
    
            /*
             * So long as we've got a path, let's configure the polling of config from Cerberus into Archaius.
             */
            if (cerberusPath != null && !cerberusPath.isEmpty()) {
                logger.info("Adding Cerberus as Configuration source. Cerberus Path = " + cerberusPath);
    
                /*
                 * Create a configuration source passing in a Cerberus client using the ArchaiusCerberusClientFactory and
                 * the path we looked up above.  This factory will attempt to resolve cerberus configuration detailts,
                 * like the URL and token from Archaius properties.
                 */
                final PolledConfigurationSource polledConfigurationSource = new NamespacedCerberusConfigurationSource(
                        ArchaiusCerberusClientFactory.getClient(), cerberusPath);
    
                /*
                 * Setup the scheduler for how often this configuration source should be refreshed.
                 */
                final AbstractPollingScheduler abstractPollingScheduler = new FixedDelayPollingScheduler(
                        POLL_INIT_DELAY, SECRETS_POLL_INTERVAL, true);
    
                /*
                 * Wrap that in a DynamicConfiguration object and add it to the configuration instance.
                 */
                final DynamicConfiguration cerberusConfig = new DynamicConfiguration(polledConfigurationSource,
                        abstractPollingScheduler);
    
                final ConcurrentCompositeConfiguration configInstance = (ConcurrentCompositeConfiguration) ConfigurationManager
                        .getConfigInstance();
    
                configInstance.addConfiguration(cerberusConfig);
            } else {
                logger.info("Property corresponding to the Cerberus path for secrets not found! "
                        + "Cerberus not added as Configuration source");
            }
        }
    }
Use a DynamicConfiguration object

With this pattern, the ConfigurationManager instance that exists is not modified in any way, and the properties stored in Cerberus are read using the cerberusConfig.getXXX methods. This might be useful in cases where the properties that need to distinct but have their names duplicated across different sources that cannot be worked around - say, in case of external dependencies using Cerberus, which use a property having the same name.

The code snippet shown below is an example of how this can be done.

    public class CerberusArchaiusModule extends AbstractModule {
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        private static final int POLL_INIT_DELAY = 1000;
        private static final int SECRETS_POLL_INTERVAL = (int) TimeUnit.HOURS.toMillis(1);
        private static final String SECRETS_PATH_PROP_NAME = "cerberus.config.path";
        private static final String SECRETS_DEFAULT_CERBERUS_PATH = "app/cerberus-demo/config";
    
        @Override
        protected void configure() {
            /*
             * First let's look up the path for where to read in Cerberus.
             * This examples show us attempting to source it from an Archaius property, but defaulting if not found.
             */
            final String cerberusPath = DynamicPropertyFactory.getInstance()
                    .getStringProperty(SECRETS_PATH_PROP_NAME, SECRETS_DEFAULT_CERBERUS_PATH)
                    .get();
    
            /*
             * So long as we've got a path, let's configure the polling of config from Cerberus into Archaius.
             */
            if (cerberusPath != null && !cerberusPath.isEmpty()) {
                logger.info("Adding Cerberus as Configuration source. Cerberus Path = " + cerberusPath);
    
                /*
                 * Create a configuration source passing in a Cerberus client using the ArchaiusCerberusClientFactory and
                 * the path we looked up above.  This factory will attempt to resolve cerberus configuration detailts,
                 * like the URL and token from Archaius properties.
                 */
                final PolledConfigurationSource polledConfigurationSource = new NamespacedCerberusConfigurationSource(
                        ArchaiusCerberusClientFactory.getClient(), cerberusPath);
    
                /*
                 * Setup the scheduler for how often this configuration source should be refreshed.
                 */
                final AbstractPollingScheduler abstractPollingScheduler = new FixedDelayPollingScheduler(
                        POLL_INIT_DELAY, SECRETS_POLL_INTERVAL, true);
    
                /*
                 * Wrap that in a DynamicConfiguration object and add it to the configuration instance.
                 */
                final DynamicConfiguration cerberusConfig = new DynamicConfiguration(polledConfigurationSource,
                        abstractPollingScheduler);
    
                /*
                 * Now we bind that dynamic configuration so that it can be used elsewhere within the context.
                 */
                bind(DynamicConfiguration.class)
                        .annotatedWith(Names.named("cerberus.config"))
                        .toInstance(cerberusConfig);
            } else {
                logger.info("Property corresponding to the Cerberus path for secrets not found! "
                        + "Cerberus not added as Configuration source");
            }
        }
    }

Further Details

Cerberus Archaius client is a small project. It only has a few classes and they are all fully documented. For further details please see the source code, including javadocs and unit tests.

Development

Run Integration Tests

First, make sure the following environment variables are set before running the Cerberus Archaius Client integration tests:

    export CERBERUS_ADDR=https://example.cerberus.com
    export CERBERUS_REGION=us-west-2
    export CERBERUS_ROOT_SDB_PATH=example/integration-tests-sdb/

Then, make sure AWS credentials have been obtained. One method is by running gimme-aws-creds:

    gimme-aws-creds

Next, in the project directory run:

    ./gradlew integration

License

Cerberus Archaius client is released under the Apache License, Version 2.0