question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Add support for secret properties

See original GitHub issue

It’s pretty common to want a separate application.properties to contains passwords and keys that shouldn’t be checked into version control. Currently this can be done with a profile but it would be nice to support something out of the box.

Issue Analytics

  • State:closed
  • Created 9 years ago
  • Reactions:7
  • Comments:20 (16 by maintainers)

github_iconTop GitHub Comments

9reactions
alimatecommented, Jun 25, 2018

How do you see that working?

Both Docker Swarm and Kubernetes can provide secrets by mounting them to a specific mounting point. Suppose the exposed secrets are mounted on /run/secrets, then the PropertySource provider should scan the /run/secrets directory and for each file add a property named after the filename with the file content being the property value.

For example, if:

> ls /run/secrets
database_password

and:

> cat /run/secrets/database_password
123

Then this new PropertySource provider should add a property named database_password with 123 as its value.

In the first two cases it’s not clear to me how Spring Boot would know where to look

We can consider sensible default values for the Mounting Point, say the /run/secrets directory. Also, we could allow developers to customize that directory.

In one of our projects, We’ve added the support for Swarm/Kubernetes secrets by registering an EnvironmentPostProcessor:

/**
 * Adding first class support for Swarm and Kubernetes Secrets in Spring Boot configuration
 * and property source hierarchy. This way any Spring Boot applications can read properties
 * from Docker Swarm/Kubernetes secrets.
 *
 * <h3>Implementation Details</h3>
 * Currently we only support Swarm style secrets file. By default when Swarm manager exposes a
 * secret to a service, it would be mounted under {@code /run/secrets/{secret_name}} file. With
 * this arrangement, each file in the {@code /run/secrets} directory would be the property name
 * and its content would be the property value.
 *
 * <a href="https://docs.docker.com/engine/swarm/secrets/">Swarm Secrets</a>
 *
 * @see EnvironmentPostProcessor
 * @see SystemEnvironmentPropertySource
 *
 * @author Ali Dehghani
 */
public class ContainerSecretsAwareEnvironmentPostProcessor implements EnvironmentPostProcessor {

    /**
     * The logger.
     */
    private static final Logger log = LoggerFactory.getLogger(ContainerSecretsAwareEnvironmentPostProcessor.class);

    /**
     * This property allows to customize the default {@code /run/secrets/} directory for
     * container property lookups.
     */
    private static final String SECRETS_DIR_PROPERTY = "container.secrets-dir";

    /**
     * The default directory inside the container to lookup for secrets that pushed by
     * the container scheduler.
     */
    private static final String DEFAULT_SECRET_DIR = "/run/secrets/";

    /**
     * Scans the secrets directory and for each file found there:
     * <pre>
     *     for each_file in secrets_dir:
     *         addProperty(filename, fileContent)
     * </pre>
     *
     * @param environment The Spring environment to customize
     * @param application The Spring Boot bootstrapper
     */
    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        String secretsDir = environment.getProperty(SECRETS_DIR_PROPERTY);
        if (secretsDir == null || secretsDir.trim().isEmpty()) secretsDir = DEFAULT_SECRET_DIR;

        log.info("About to read container secrets from {}", secretsDir);
        Map<String, Object> readProperties = readFromSharedSecrets(secretsDir);

        if (!readProperties.isEmpty()) {
            log.info("Adding {} secrets from container scheduler", readProperties.size());

            SystemEnvironmentPropertySource propertySource =
                new SystemEnvironmentPropertySource("container-secrets", readProperties);

            // A property with lowest possible priority
            environment.getPropertySources().addLast(propertySource);
        }
    }

    /**
     * Scans the {@code secretsDir} directory and for each file, add a property named after the
     * file paired with its content as the property value.
     *
     * @param secretsDir Represents the secrets directory
     * @return Map of property names and property values.
     */
    private Map<String, Object> readFromSharedSecrets(String secretsDir) {
        Path directory;
        try {
            directory = Paths.get(secretsDir);
        } catch (Exception e) {
            log.warn("The provided container secrets directory was invalid: {}", secretsDir);
            return Collections.emptyMap();
        }

        if (!Files.exists(directory)) {
            log.warn("The provided container secrets directory not found: {}", secretsDir);
            return Collections.emptyMap();
        }

        try {
            return Files.list(directory)
                        .filter(this::isFile)
                        .collect(toMap(this::filename, this::content));
        } catch (Exception e) {
            log.warn("Failed to read secrets from secrets directory", e);
            return Collections.emptyMap();
        }
    }

    /**
     * @param path The path to inspect
     * @return Is the given {@code path} represents a file?
     */
    private boolean isFile(Path path) {
        return path.toFile().isFile();
    }

    /**
     * @param path The path to extract its filename
     * @return The filename
     */
    private String filename(Path path) {
        return path.getFileName().toString();
    }

    /**
     * @param path Represents a path to a file
     * @return The corresponding file content
     */
    private String content(Path path) {
        try {
            return Files.lines(path).filter(line -> !line.trim().isEmpty()).collect(joining());
        } catch (IOException e) {
            log.warn("Failed to read the secret file", e);
            return "";
        }
    }
}

2reactions
spencergibbcommented, Jun 25, 2018

We are working on that in spring cloud kubernetes that will be part of the Greenwich release train

Read more comments on GitHub >

github_iconTop Results From Across the Web

Spring Boot Secret Properties: How to store secrets in your ...
In this tutorial, you will learn how to use Spring Boot secret properties in your next application. When you define your own configuration ......
Read more >
Secrets | Kubernetes
A Secret is an object that contains a small amount of sensitive data such as a password, a token, or a key. Such...
Read more >
Safe storage of app secrets in development in ASP.NET Core
In Visual Studio, right-click the project in Solution Explorer, and select Manage User Secrets from the context menu. This gesture adds a ...
Read more >
Secret Backends - Spring
Spring Cloud Vault supports both Key-Value secret backends, the versioned (v2) and unversioned ... The application name is determined by the properties:.
Read more >
AWS Secret Manager Service as application properties with ...
Secrets can be database credentials, passwords, third-party API keys, and even arbitrary text. You can store and control access to these secrets centrally...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found