【应用安全】使用Testcontainers框架测试Spring引导与Vault和Postgres的集成

Chinese, Simplified

需要针对流行的第三方解决方案(如数据库)为应用程序编写集成测试吗?

 

我已经写了许多文章,其中使用Docker容器运行一些与示例应用程序集成的第三方解决方案。如果没有Docker容器,为这样的应用程序构建集成测试可能不是一项容易的任务。特别是当我们的应用程序与数据库、消息代理或其他一些流行的工具集成时。如果您计划构建这样的集成测试,那么您一定要查看testcontainer。

Testcontainers是一个支持JUnit测试的Java库,它为运行公共数据库、Selenium web浏览器或任何其他可以在Docker容器中运行的实例提供了一种快速和轻量级的方法。它为最流行的关系数据库和NoSQL数据库(如Postgres、MySQL、Cassandra或Neo4j)提供模块。它还允许运行流行的产品,如Elasticsearch、Kafka、Nginx或HashiCorp's Vault。今天,我将向您展示一个更高级的JUnit测试示例,它使用testcontainer检查Spring Boot/Spring Cloud应用程序、Postgres数据库和Vault之间的集成。

出于这个示例的目的,我们将使用我之前的一篇文章Secure Spring Cloud Microservices with Vault和Nomad中描述的情况。让我们回顾一下那个用例。

我在那里描述了如何使用一个非常有趣的Vault特性,称为secret engine,来动态生成数据库用户凭证。我在Spring引导应用程序中使用Spring Cloud Vault模块来自动集成Vault的这个特性。实现的机制非常简单。应用程序在启动时尝试连接到Postgres数据库之前调用Vault secret engine。Vault通过secret engine与Postgres集成,这就是为什么它创建了一个对Postgres具有足够特权的用户。然后,生成的凭证会自动注入到自动配置的Spring Boot属性中,这些属性用于连接数据库Spring .datasource。用户名和spring.datasource.password。下图说明了所描述的解决方案:

好的,我们知道它是如何工作的,现在的问题是如何自动测试它。使用testcontainer,只需几行代码就可以了。

1. 构建应用程序

我们先简单介绍一下应用程序代码;这很简单。下面是构建一个公开REST API并与Postgres和Vault集成的应用程序所需的依赖项列表。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-vault-config-databases</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>42.2.5</version>
</dependency>

 

应用程序连接到Postgres,通过Spring Cloud Vault与Vault集成,并在启动时自动创建/更新表。

spring:
  application:
    name: callme-service
  cloud:
    vault:
      uri: http://192.168.99.100:8200
      token: ${VAULT_TOKEN}
      postgresql:
        enabled: true
        role: default
        backend: database
  datasource:
    url: jdbc:postgresql://192.168.99.100:5432/postgres
  jpa.hibernate.ddl-auto: update

它公开单个端点。下面的方法负责处理传入的请求。它只是将一条记录插入数据库,并返回一个带有所插入记录的应用程序名称、版本和ID的响应。

@RestController
@RequestMapping("/callme")
public class CallmeController {

 
    private static final Logger LOGGER = LoggerFactory.getLogger(CallmeController.class);

 
    @Autowired
    Optional<BuildProperties> buildProperties;
    @Autowired
    CallmeRepository repository;

 
    @GetMapping("/message/{message}")
    public String ping(@PathVariable("message") String message) {
        Callme c = repository.save(new Callme(message, new Date()));
        if (buildProperties.isPresent()) {
            BuildProperties infoProperties = buildProperties.get();
            LOGGER.info("Ping: name={}, version={}", infoProperties.getName(), infoProperties.getVersion());
            return infoProperties.getName() + ":" + infoProperties.getVersion() + ":" + c.getId();
        } else {
            return "callme-service:"  + c.getId();
        }

 

2. 使能Testcontainers

要为我们的项目启用testcontainer,我们需要包含Maven pom.xml的一些依赖项。我们有专门的模块为Postgres和Vault。我们还包括一个Spring引导测试依赖项,因为我们想测试整个Spring引导应用程序。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>vault</artifactId>
    <version>1.10.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <version>1.10.5</version>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>postgresql</artifactId>
    <version>1.10.5</version>
    <scope>test</scope>
</dependency>

3.运转Vault Test 容器

Testcontainers框架支持JUnit 4/JUnit 5和Spock。如果Vault容器使用@Rule或@ClassRule注释,则可以在测试之前启动它。默认情况下,它使用0.7版本,但是我们可以使用最新版本1.0.2覆盖它。我们还可以设置一个根令牌,然后Spring Cloud Vault需要它来与Vault集成。

@ClassRule
public static VaultContainer vaultContainer = new VaultContainer<>("vault:1.0.2")
    .withVaultToken("123456")
    .withVaultPort(8200);

 

在测试类上启动JUnit测试之前,可以覆盖该根令牌。

@RunWith (SpringRunner.class)

@SpringBootTest (webEnvironment = SpringBootTest.WebEnvironment。RANDOM_PORT, properties = {

“spring.cloud.vault.token = 123456”

})

公共类CallmeTest{…}

 

4. 运行Postgres测试容器

作为@ClassRule的替代方法,我们可以在测试中的@BeforeClass或@Before方法中手动启动容器。使用这种方法,您还必须在@AfterClass或@After方法中手动停止它。我们手动启动Postgres容器,因为默认情况下,它是在动态生成的端口上公开的,在启动测试之前,需要为Spring Boot应用程序设置这个端口。侦听端口由在PostgreSQLContainer上调用的getFirstMappedPort方法返回。

private static PostgreSQLContainer postgresContainer = new PostgreSQLContainer()
    .withDatabaseName("postgres")
    .withUsername("postgres")
    .withPassword("postgres123");

 
@BeforeClass
public static void init() throws IOException, InterruptedException {
    postgresContainer.start();
    int port = postgresContainer.getFirstMappedPort();
    System.setProperty("spring.datasource.url", String.format("jdbc:postgresql://192.168.99.100:%d/postgres", postgresContainer.getFirstMappedPort()));
    // ...
}

 
@AfterClass
public static void shutdown() {
    postgresContainer.stop();
}

5. 集成Vault和Postgres容器

一旦我们成功地启动了Vault和Postgres容器,我们需要通过Vault secret engine集成它们。首先,我们需要启用数据库secret engine Vault。之后,我们必须配置到Postgres的连接。最后一步是配置一个角色。角色是一个逻辑名称,它映射到用于生成这些凭证的策略。所有这些操作都可以使用Vault命令执行。您可以使用execInContainer方法在Vault容器上启动命令。Vault配置命令应该在Postgres容器启动后立即执行。

@BeforeClass
public static void init() throws IOException, InterruptedException {
    postgresContainer.start();
    int port = postgresContainer.getFirstMappedPort();
    System.setProperty("spring.datasource.url", String.format("jdbc:postgresql://192.168.99.100:%d/postgres", postgresContainer.getFirstMappedPort()));
    vaultContainer.execInContainer("vault", "secrets", "enable", "database");
    String url = String.format("connection_url=postgresql://{{username}}:{{password}}@192.168.99.100:%d?sslmode=disable", port);
    vaultContainer.execInContainer("vault", "write", "database/config/postgres", "plugin_name=postgresql-database-plugin", "allowed_roles=default", url, "username=postgres", "password=postgres123");
    vaultContainer.execInContainer("vault", "write", "database/roles/default", "db_name=postgres",
        "creation_statements=CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';GRANT SELECT, UPDATE, INSERT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";GRANT USAGE,  SELECT ON ALL SEQUENCES IN SCHEMA public TO \"{{name}}\";",
        "default_ttl=1h", "max_ttl=24h");
}

 

6. 运行应用程序测试

最后,我们可以运行应用程序测试。我们只调用应用程序使用TestRestTemplate公开的单个端点,并验证输出。

@Autowired
TestRestTemplate template;

 
@Test
public void test() {
    String res = template.getForObject("/callme/message/{message}", String.class, "Test");
    Assert.assertNotNull(res);
    Assert.assertTrue(res.endsWith("1"));
}

 

如果您对测试期间究竟发生了什么感兴趣,您可以在测试方法中设置断点并手动执行docker ps命令。

 

原文:https://dzone.com/articles/testing-spring-boot-integration-with-vault-and-pos

本文:https://pub.intelligentx.net/node/735

讨论:请加入知识星球或者小红圈【首席架构师圈】

SEO Title
Testing Spring Boot Integration With Vault and Postgres Using Testcontainers Framework