在运行Spring Boot应用程序的Docker容器中注入凭证

427 阅读4分钟

Spring Boot中的profile概念使得提供针对运行环境的配置变得非常容易。一个典型的设置可能包括本地开发环境、测试环境、暂存和生产环境的配置。你所需要做的就是在classpath上为每个环境提供一个属性文件,例如application-dev.propertiesapplication-prod.properties 。在运行时,你可以通过命令行选项或环境变量告诉Spring Boot选择哪个配置文件。

现在,不同的环境需要不同的凭证。比方说,你的应用程序在开发时使用嵌入式H2数据库,在所有其他环境中使用PostgreSQL。感觉很自然,只需在配置文件的属性文件中添加用户名/密码或访问令牌。在构建Spring Boot应用程序的二进制文件、可执行JAR或WAR文件时,这些文件将简单地包含在可交付的文件中。

根据你计划分享二进制文件的方式和应用的部署策略,这种方法可能会引起安全问题。是否应该允许任何能够访问该文件的人破解存档并访问纯文本凭证?如果你为你的应用程序建立一个Docker镜像,并在Docker注册表上分享它呢?你可能已经猜到了。这可能是一个更好的主意,将凭证外部化并在运行时注入,以消除潜在的安全漏洞。

在这篇博文中,我将向你展示如何使用Docker secrets来在Docker Swarm中创建凭证。我们将在该Swarm上运行一个分布在多个副本中的Spring Boot应用,并在为该应用创建服务时才注入凭证。

Docker secrets是一项企业功能。不幸的是,如果不在Swarm中分发你的应用程序,你就无法使用Docker secrets。在未来的一篇文章中,我将讨论存储证书的其他选择,如HashiCorp的Vault

读取Docker secrets文件

在Spring Boot应用程序的不同环境之间进行切换很容易,即使它是在Docker容器中运行的。只需在启动容器时传递环境变量SPRING_PROFILES_ACTIVE ,并将其指向所需的配置文件名称。此外,如果Docker secrets文件确实存在的话,我们要从该文件中读取凭证。默认情况下,当在Docker Swarm的一个节点上运行时,Docker secrets在目录/run/secrets

读取Docker机密文件的一个很好的方法是实现一个EnvironmentPostProcessor。假设我们想读取一个数据库密码并将其用于我们应用程序的数据源。列表1中所示的类正好实现了这一点。

DockerSecretsDatabasePasswordProcessor.java

package com.bmuschko.todo.webservice.env;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.env.EnvironmentPostProcessor;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StreamUtils;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.Properties;

public class DockerSecretsDatabasePasswordProcessor implements EnvironmentPostProcessor {
    private final Logger logger = LoggerFactory.getLogger(DockerSecretsDatabasePasswordProcessor.class);

    @Override
    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {
        Resource resource = new FileSystemResource("/run/secrets/db-password");

        if (resource.exists()) {
            try {
                if (logger.isInfoEnabled()) {
                    logger.info("Using database password from injected Docker secret file");
                }

                String dbPassword = StreamUtils.copyToString(resource.getInputStream(), Charset.defaultCharset());
                Properties props = new Properties();
                props.put("spring.datasource.password", dbPassword);
                environment.getPropertySources().addLast(new PropertiesPropertySource("dbProps", props));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

清单1.从Docker secrets文件中读取和应用数据库凭证

实现EnvironmentPostProcessor 是不够的。你必须在一个名为spring.factories 的文件中注册该类。对于Maven和Gradle项目,该文件通常位于src/main/resources ,在创建可执行二进制文件时与应用程序捆绑在一起。

META-INF/spring.plants

org.springframework.boot.env.EnvironmentPostProcessor=com.bmuschko.todo.webservice.env.DockerSecretsDatabasePasswordProcessor

该应用程序已经准备就绪。接下来,我们来看看容器的基础设施。

创建一个Docker秘密

使用Docker秘密的前提条件是有一个Docker Swarm。一个Docker Swarm必须至少有一个管理节点。此外,你可以有任何数量的工作节点。如果你想了解更多关于容错和负载平衡的信息,请参考Docker文档

你可以在Swarm管理器上用一条命令创建一个Docker秘密。下面的命令创建了包含密码prodpwd 的秘密db-password

$ printf "prodpwd" | docker secret create db-password -

创建秘密后,你应该可以列出它。

$ docker secret ls
ID                          NAME                DRIVER              CREATED             UPDATED
nskgie2gozso364hwl92ne3og   db-password                             12 days ago         12 days ago

我们创建了一个秘密。让我们把它注入运行我们应用程序的容器中。

使用Docker秘密

在Docker中,你可以在服务的帮助下将一个应用程序分布在多个节点上。创建服务时,你可以提供设置环境变量和Docker秘密的选项。

假设我们想在生产环境中运行一个Spring Boot应用程序。首先,我们用--env SPRING_PROFILES_ACTIVE=prod 激活配置文件。其次,我们通过--secret db-password 传入带有名称的Docker秘密。下面你可以看到创建一个有五个副本的Docker服务的完整命令。

$ docker service create --name todo-web-service --publish 8080:8080 --replicas 5 --secret db-password --env SPRING_PROFILES_ACTIVE=prod bmuschko/todo-web-service:latest

一切就绪。你的Spring Boot应用程序应该能够读取Docker秘密并使用它来连接到数据库。

总结

无论你是在容器中还是作为一个普通的可执行JAR文件运行,都不应该把凭证放在Spring Boot应用的二进制文件中。Docker secrets可以帮助保持凭证的安全性。在Docker Swarm环境中,很容易将秘密注入正在运行的容器中。从Spring Boot应用中读取秘密只需要实现一个后置处理器。