• 基于Spring WebFlux和Vault PKI的SSL


    在本文中,我们将学习如何配置Vault PKI引擎并将其与Spring WebFlux集成。使用Vault PKI,您可以轻松生成由CA签名的X.509证书。然后,您的应用程序可以通过REST API获得证书。它的TTL相对较短。每个应用程序实例都是唯一的。此外,我们还可以使用SpringVault模板简化与Vault API的集成。

    让我们再多说一点关于安全库的事。它允许您使用UI、CLI或HTTP API保护、存储和控制对令牌、密码、证书和加密密钥的访问。这是一个非常强大的工具。使用Vault,而不是传统的方法,您可以以更动态、云本地的方式管理您的安全性。例如,可以将Vault与数据库后端集成,然后动态生成用户登录名和密码。此外,对于Spring Boot应用程序,您可以利用Spring Cloud Vault项目。

    还不止这些。您可以将Vault与来自Hashicorp的其他工具(如Consor或Nomad)集成。换句话说,它允许我们以安全的方式构建云本地平台。

    源代码

    如果您想自己尝试,您可以随时查看我的源代码。为了做到这一点,您需要克隆我的存储库示例SpringCloudSecurity( https://github.com/piomin/sample-spring-cloud-security.git )。然后,您应该转到网关服务目录,并按照我在下一节中的说明进行操作。示例应用程序充当微服务的API网关。我们使用Spring Cloud Gateway。由于它是建立在Spring WebFlux之上的,所以这个例子非常适合我们当前的文章。

    1. 运行Vault

    我们将在开发模式下在Docker容器中运行Vault。在该模式下运行的服务器不需要任何进一步的设置,启动后即可使用。启动后,我们的Vault实例在端口 8200 上可用。本文中使用的Vault版本为1.7.1。

    $ docker run --cap-add=IPC_LOCK -d --name vault -p 8200:8200 vault

    可以使用不同的方法登录,但最适合我们的方式是通过令牌。为此,我们必须使用命令 docker logs vault 显示容器日志,然后复制根令牌,如下所示。

    最后,我们可以登录到Vault web控制台。

    2. 启用和配置Vault PKI

    有两种方法可以启用和配置Vault PKI:使用CLI或通过UI。大多数文章都描述了配置PKI引擎所需的CLI命令列表。但是,我将使用Vault UI来实现这一点。首先,让我们在主站点上启用一个新引擎。

    然后,我们需要选择一种类型的引擎来启用。在我们的例子中,它是PKI证书的选项。

    在创建过程中,让我们保留一个默认名称pki。然后,我们需要导航到新启用的引擎并创建一个新角色。角色用于生成证书。我的角色的名称是默认的。这个名称很重要,因为我们必须使用 VaultTemplate 从代码中调用它。

    我们角色中使用的密钥类型是 rsa 。

    在创建它之前,我们应该设置一些重要参数。其中之一是TTL,设置为3天。另外,不要忘了检查字段是否允许任何名称,以及是否需要通用名称。它们都与证书中的CN字段相关。因为我们将在CN字段中存储一个用户名,所以我们需要允许它的任何名称。

    创建角色后,我们需要配置CA。为此,我们应该首先切换到“配置”选项卡,然后单击“配置”按钮。

    之后,让我们选择配置CA。

    最后,我们可以创建一个新的CA证书。我们应该将根值保留为CA类型,将内部值保留为类型。默认密钥格式为 pem 。我们还可以为CA证书设置一个通用名称。对于角色和CA,值得填写其他字段,例如组织或组织单位的名称。

    3. Spring WebFlux与Vault PKI的集成

    让我们从依赖性开始。我们需要包括一个用于反应式API的Spring WebFlux启动器和一个用于保护API的Spring Security启动器。spring Vault core可以提供与Vault API的集成。为了能够使用Spring Vault启动应用程序,我还必须包含Jackson库。

    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-cloud-starter-webflux</artifactId>
    </dependency>
    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-core</artifactId>
    </dependency>
    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-databind</artifactId>
    </dependency>
    <dependency>
       <groupId>com.fasterxml.jackson.core</groupId>
       <artifactId>jackson-annotations</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.vault</groupId>
       <artifactId>spring-vault-core</artifactId>
    </dependency>
    <dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-security</artifactId>
    </dependency>

    然后,让我们配置一个Vault模板 bean 。它应该使用 http 方案和从配置中注入的身份验证令牌。

    @Value("vault.token")
    private String vaultToken;
    
    @Bean
    VaultTemplate vaultTemplate() {
       VaultEndpoint e =  new VaultEndpoint();
       e.setScheme("http");
       VaultTemplate template = new VaultTemplate(e, new TokenAuthentication(vaultToken));
       return template;
    }

    Vault模板为与PKI引擎的交互提供专用支持。我们只需要调用传递PKI引擎名称的方法 opsForPki 来获得Vault PKI操作实例(1)。然后,我们需要使用Vault CertificateRequest构建一个证书请求。我们可以设置几个参数,但最重要的是CN和证书TTL(2)。最后,我们应该调用issueCertificate方法,传递请求和在Vault PKI上配置的角色的名称(3)。我们的证书已成功生成。现在,我们只需要从响应中获得它。生成的证书、CA证书和私钥在方法返回的 CertificateBundle 对象中可用。

    private CertificateBundle issueCertificate() throws Exception {
       VaultPkiOperations pkiOperations = vaultTemplate.opsForPki("pki"); // (1)
       VaultCertificateRequest request = VaultCertificateRequest.builder()
            .ttl(Duration.ofHours(12))
            .commonName("localhost")
            .build(); // (2)
       VaultCertificateResponse response = pkiOperations.issueCertificate("default", request); // (3)
       CertificateBundle certificateBundle = response.getRequiredData(); // (4)
    
       log.info("Cert-SerialNumber: {}", certificateBundle.getSerialNumber());
       return certificateBundle;
    }

    4. 启用SpringWebFlux安全性

    在上一节中,我们已经将SpringWebFlux与Vault PKI集成。最后,我们可以进入实现的最后一步——基于X.509证书启用安全性。为此,我们需要创建一个 @Configuration 类。应使用 @EnableWebFluxSecurity (1)对其进行注释。我们还需要通过实现主体提取器从证书中获取用户名。我们将使用 SubjectDNX509 (2)和从CN字段读取数据的正确正则表达式。最终配置禁用CSRF、基本身份验证,并使用X509证书启用SSL(3)。我们还需要用一个用户名 piotrm 提供 UserDetails 接口(4)的实现。

    @Configuration
    @EnableWebFluxSecurity // (1)
    public class SecurityConfig {
    
       @Bean
       public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
          SubjectDnX509PrincipalExtractor principalExtractor =
                 new SubjectDnX509PrincipalExtractor(); // (2)
          principalExtractor.setSubjectDnRegex("CN=(.*?)(?:,|$)");
    
          return http.csrf().disable()
                 .authorizeExchange(exchanges -> 
                        exchanges.anyExchange().authenticated())
                 .x509()
                    .principalExtractor(principalExtractor)
                 .and()
                    .httpBasic().disable().build(); // (3)
       }
    
       @Bean
       public MapReactiveUserDetailsService users() { // (4)
          UserDetails user1 = User.builder()
                 .username("piotrm")
                 .password("{noop}1234")
                 .roles("USER")
                 .build();
          return new MapReactiveUserDetailsService(user1);
       }
    }

    在最后一步中,我们需要在运行时覆盖Netty服务器SSL配置。我们的定制者应该实现 WebServerFactoryCustomizer 接口,并使用 NettyReactiveWebServerFactory 。在 customize 方法中,我们首先调用负责在Vault中生成证书的方法 issueCertificate (您可以参考上一节了解该方法的实现)(1)。 CertificateBundle 包含所有必需的数据。我们可以在其上调用 createKeyStore 方法来创建密钥库(2),然后将其保存在文件(3)中。

    要覆盖Netty SSL设置,我们应该使用SSL对象。需要启用客户端身份验证(4)。我们还将设置当前创建的密钥库(5)的位置。之后,我们可以继续创建信任库。发卡机构证书可从 CertificateBundle (6)处获得。然后我们应该创建一个新的密钥库,并将CA证书设置为其中的一个条目(7)。最后,我们将把信任库保存到文件中,并在Ssl对象中设置其位置。

    @Component
    @Slf4j
    public class GatewayServerCustomizer implements 
             WebServerFactoryCustomizer<NettyReactiveWebServerFactory> {
    
       @SneakyThrows
       @Override
       public void customize(NettyReactiveWebServerFactory factory) {
          String keyAlias = "vault";
          CertificateBundle bundle = issueCertificate(); // (1)
          KeyStore keyStore = bundle.createKeyStore(keyAlias); // (2)
          String keyStorePath = saveKeyStoreToFile("server-key.pkcs12", keyStore); // (3)
    
          Ssl ssl = new Ssl();
          ssl.setEnabled(true);
          ssl.setClientAuth(Ssl.ClientAuth.NEED); // (4)
    
          ssl.setKeyStore(keyStorePath); // (5)
          ssl.setKeyAlias(keyAlias);
          ssl.setKeyStoreType(keyStore.getType());
          ssl.setKeyPassword("");
          ssl.setKeyStorePassword("123456");
    
          X509Certificate caCert = bundle.getX509IssuerCertificate(); // (6)
          log.info("CA-SerialNumber: {}", caCert.getSerialNumber());
          KeyStore trustStore = KeyStore.getInstance("pkcs12");
          trustStore.load(null, null);
          trustStore.setCertificateEntry("ca", caCert); // (7)
          String trustStorePath = saveKeyStoreToFile("server-trust.pkcs12", trustStore); // (8)
    
          ssl.setTrustStore(trustStorePath); // (9)
          ssl.setTrustStorePassword("123456");
          ssl.setTrustStoreType(trustStore.getType());
    
          factory.setSsl(ssl);
          factory.setPort(8443);
       }
    }

    5. 使用Vault PKI测试Spring WebFlux

    让我们运行示例应用程序。它在 8443 端口下可用。我们将使用curl工具进行测试。在这样做之前,我们需要生成一个带有私钥的客户端证书。让我们再次转到Vault UI。如果单击默认Vault UI,将重定向到负责生成证书的表单,如下所示。在 CommonName 字段中,我们应该提供 UserDetails 实现中配置的测试用户名。对我来说,这是piotrm。另外,不要忘记设置正确的TTL。

    生成证书后,您将被重定向到带有结果的站点。首先,您应该将字符串与证书一起复制,并将其保存到文件中。对我来说,它是 piotrm.crt 。您还可以显示生成的私钥的内容。然后,执行与证书相同的操作。我的文件名是 piotrm.key 。

    最后,我们可以向示例应用程序发送一个测试请求,传递密钥和证书文件的名称:

    $ curl https://localhost:8443/hello -v --key piotrm.key --cert piotrm.crt
  • 相关阅读:
    FRNet:Feature Reconstruction Network for RGB-D Indoor Scene Parsing实验补充
    GBASE 8s 数据库的恢复
    实现CNN图像的识别和训练通过tensorflow框架对cifar10数据集等方法的处理
    NC9 二叉树中和为某一值的路径(一)
    [React] react-router-dom的v5和v6
    了解.NET Framework中自带的泛型委托Predicate和Comparison
    实现深拷贝
    邮件功能-python中的SMTP协议邮件发送
    Unix 操作系统背后的女程序员 Lorinda Cherry 去世,享年 78 岁
    [HDLBits] Edgecapture
  • 原文地址:https://blog.csdn.net/JavaMonsterr/article/details/125525114