认证方法

不同的组织对安全和认证有不同的要求。Vault 通过提供多种认证方法反映了这一需求。Spring Vault 支持多种认证机制。

外部化登录凭据

首次访问安全系统被称为安全引入。任何客户端都需要临时或永久凭据来访问 Vault。外部化凭据是一个保持代码高可维护性的良好模式,但也带来了增加泄露的风险。

将登录凭据泄露给任何一方都允许登录 Vault 并访问基础角色允许的 Secrets。选择合适的客户端认证并将凭据注入应用程序需要进行风险评估。

Spring 的 PropertySource 抽象非常适合将配置保留在应用程序代码之外。您可以使用系统属性、环境变量或属性文件来存储登录凭据。每种方法都有其各自的特性。请记住,命令行和环境变量属性可以通过适当的操作系统访问级别进行内省(查看)。

示例 1. 将 vault.token 外部化到属性文件
@PropertySource("configuration.properties")
@Configuration
public class Config extends AbstractVaultConfiguration {

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication(getEnvironment().getProperty("vault.token"));
    }
}
Spring 提供了多种获取 Environment 的方式。当使用 VaultPropertySource 时,通过 @Autowired Environment environment 进行注入将无法提供 Environment,因为环境 bean 仍在构建中,而自动装配发生在更晚的阶段。您的配置类应该实现 ApplicationContextAware 并从 ApplicationContext 获取 Environment

请参见 SecurePropertyUsage.java,了解在组件和其他属性源中引用属性的示例。

Token 认证

Tokens 是 Vault 中核心的认证方法。Token 认证需要提供一个静态 Token。

Token 认证是默认的认证方法。如果 Token 泄露给未经授权的方,则该方将获得 Vault 的访问权限,并可以访问预期客户端的 Secrets。

通常,Token 认证用于 Token 在外部创建和续期的场景(例如 HashiCorp Vault service broker)。根据实际设置,您可能希望或不希望进行 Token 续期和撤销。请参见 LifecycleAwareSessionManager 了解有关 TTL 和 Token 撤销的详细信息。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {
        return new TokenAuthentication("…");
    }

    // …
}

另请参见

AppId 认证

AppId 认证已被 Vault 弃用。请改用 AppRole 认证

Vault 支持 AppId 认证,该认证由两个难以猜测的 Token 组成。AppId 默认为静态配置的 spring.application.name。第二个 Token 是 UserId,由应用程序确定,通常与运行时环境相关。IP 地址、Mac 地址或 Docker 容器名称都是很好的例子。Spring Vault 支持 IP 地址、Mac 地址和静态 UserId(例如通过系统属性提供)。IP 地址和 Mac 地址表示为十六进制编码的 SHA256 散列。

基于 IP 地址的 UserId 使用本地主机的 IP 地址。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {
        AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
                .appId("myapp")
                .userIdMechanism(new IpAddressUserId())
                .build();

        return new AppIdAuthentication(options, restOperations());
    }

    // …
}

从命令行生成基于 IP 地址的 UserId 的对应命令是

$ echo -n 192.168.99.1 | sha256sum
包含 echo 的换行符会导致不同的散列值,因此请务必包含 -n 标志。

基于 Mac 地址的 UserId 从绑定到本地主机的设备获取其网络设备。配置还允许指定一个 network-interface 提示来选择正确的设备。network-interface 的值是可选的,可以是接口名称或接口索引(从 0 开始)。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AppIdAuthenticationOptions options = AppIdAuthenticationOptions.builder()
                .appId("myapp")
                .userIdMechanism(new MacAddressUserId())
                .build();

        return new AppIdAuthentication(options, restOperations());
    }

    // …
}

从命令行生成基于 Mac 地址的 UserId 的对应命令是

$ echo -n 0AFEDE1234AC | sha256sum
Mac 地址指定为大写且不含冒号。包含 echo 的换行符会导致不同的散列值,因此请务必包含 -n 标志。

自定义 UserId

一种更高级的方法允许您实现自己的 AppIdUserIdMechanism。此类别必须位于您的类路径中,并且必须实现 org.springframework.vault.authentication.AppIdUserIdMechanism 接口和 createUserId 方法。Spring Vault 将在每次使用 AppId 进行认证以获取 Token 时,通过调用 createUserId 来获取 UserId。

MyUserIdMechanism.java
public class MyUserIdMechanism implements AppIdUserIdMechanism {

  @Override
  public String createUserId() {

    String userId = …
    return userId;
  }
}

AppRole 认证

AppRole 允许机器认证,类似于已弃用(自 Vault 0.6.1 起)的 AppId 认证。AppRole 认证由两个难以猜测的(秘密)Token 组成:RoleId 和 SecretId。

Spring Vault 支持 AppRole 认证,可以通过仅提供 RoleId,或同时提供 SecretId 并从 Vault 获取 RoleId/SecretId(带响应解包的 push 和 pull 模式)。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
                .roleId(RoleId.provided("…"))
                .secretId(SecretId.wrapped(VaultToken.of("…")))
                .build();

        return new AppRoleAuthentication(options, restOperations());
    }

    // …
}

Spring Vault 还支持完整的 pull 模式:如果未提供 RoleId 和 SecretId,Spring Vault 将使用角色名和初始 Token 来获取它们。初始 Token 可能与 TTL 和使用限制相关联。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        VaultToken initialToken = VaultToken.of("…");
        AppRoleAuthenticationOptions options = AppRoleAuthenticationOptions.builder()
                .appRole("…")
                .roleId(RoleId.pull(initialToken))
                .secretId(SecretId.pull(initialToken))
                .build();

        return new AppRoleAuthentication(options, restOperations());
    }

    // …
}

AWS-EC2 认证

aws-ec2 认证后端为 AWS EC2 实例提供了安全引入机制,允许自动获取 Vault Token。与大多数 Vault 认证后端不同,此后端不需要首先部署或配置安全性敏感的凭据(Tokens、用户名/密码、客户端证书等)。相反,它将 AWS 视为可信第三方,并使用唯一代表每个 EC2 实例的加密签名的动态元数据信息。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {
        return new AwsEc2Authentication(restOperations());
    }

    // …
}

AWS-EC2 认证默认启用 nonce,以遵循首次使用时信任(TOFU)原则。任何获取 PKCS#7 身份元数据的未经授权方都可以对 Vault 进行认证。

在首次登录期间,Spring Vault 会生成一个 nonce,并将其与实例 Id 一起存储在认证后端。重新认证需要发送相同的 nonce。任何其他方没有该 nonce,可以在 Vault 中引发警报以进行进一步调查。

nonce 保存在内存中,并在应用程序重启时丢失。

AWS-EC2 认证角色是可选的,默认使用 AMI。您可以通过在 AwsEc2AuthenticationOptions 中设置来配置认证角色。

AWS-IAM 认证

aws 认证后端允许使用现有的 AWS IAM 凭据登录 Vault。

AWS IAM 认证创建一个签名 HTTP 请求,该请求由 Vault 执行,以便使用 AWS STS 的 GetCallerIdentity 方法获取签名者的身份。AWSv4 签名需要 IAM 凭据。

IAM 凭据可以从运行时环境或外部提供。分配了 IAM 主体的运行时环境(例如 AWS-EC2、Lambda 和 ECS)不需要针对客户端配置凭据,可以从其元数据源获取。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AwsIamAuthenticationOptions options = AwsIamAuthenticationOptions.builder()
                .credentials(new BasicAWSCredentials(…)).build();

        return new AwsIamAuthentication(options, restOperations());
    }

    // …
}
示例 2. 使用 AWS-EC2 实例配置文件作为凭据源
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AwsIamAuthenticationOptions options = AwsIamAuthenticationOptions.builder()
                .credentialsProvider(InstanceProfileCredentialsProvider.getInstance()).build();

        return new AwsIamAuthentication(options, restOperations());
    }

    // …
}

AwsIamAuthentication 需要 AWS Java SDK 依赖项 (com.amazonaws:aws-java-sdk-core),因为认证实现使用 AWS SDK 类型进行凭据和请求签名。

您可以通过 AwsIamAuthenticationOptions 配置认证。

另请参见

Azure (MSI) 认证

azure 认证后端为 Azure VM 实例提供了安全引入机制,允许自动获取 Vault Token。与大多数 Vault 认证后端不同,此后端不需要首先部署或配置安全性敏感的凭据(Tokens、用户名/密码、客户端证书等)。相反,它将 Azure 视为可信第三方,并使用可以绑定到 VM 实例的托管服务身份和实例元数据信息。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        AzureMsiAuthenticationOptions options = AzureMsiAuthenticationOptions.builder()
                    .role(…).build();

        return new AzureMsiAuthentication(options, restOperations());
    }

    // …
}

Azure 认证需要关于 VM 环境的详细信息(订阅 Id、资源组名、VM 名)。这些详细信息可以通过 AzureMsiAuthenticationOptionsBuilder 进行配置。如果未配置,AzureMsiAuthentication 将查询 Azure 的实例元数据服务以获取这些详细信息。

另请参见

GCP-GCE 认证

gcp 认证后端允许使用现有的 GCP (Google Cloud Platform) IAM 和 GCE 凭据登录 Vault。

GCP GCE (Google Compute Engine) 认证为服务账户创建了一个 JSON Web Token (JWT) 形式的签名。Compute Engine 实例的 JWT 使用 实例标识从 GCE 元数据服务获取。此 API 创建一个 JSON Web Token,可用于确认实例身份。

与大多数 Vault 认证后端不同,此后端不需要首先部署或配置安全性敏感的凭据(Tokens、用户名/密码、客户端证书等)。相反,它将 GCP 视为可信第三方,并使用唯一代表每个 GCP 服务账户的加密签名的动态元数据信息。

您可以通过 GcpComputeAuthenticationOptions 配置认证。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        GcpComputeAuthenticationOptions options = GcpComputeAuthenticationOptions.builder()
				.role(…).build();

		GcpComputeAuthentication authentication = new GcpComputeAuthentication(options,
				restOperations());
    }

    // …
}

另请参见

GCP-IAM 认证

gcp 认证后端允许使用现有的 GCP (Google Cloud Platform) IAM 和 GCE 凭据登录 Vault。

GCP IAM 认证为服务账户创建了一个 JSON Web Token (JWT) 形式的签名。通过调用 GCP IAM 的 projects.serviceAccounts.signJwt API 获取服务账户的 JWT。调用者对 GCP IAM 进行认证,从而证明其身份。此 Vault 后端将 GCP 视为可信第三方。

IAM 凭据可以从运行时环境或外部提供(例如 JSON 格式)。JSON 是首选形式,因为它携带调用 projects.serviceAccounts.signJwt 所需的项目 ID 和服务账户标识符。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        GcpIamCredentialsAuthenticationOptions options = GcpIamCredentialsAuthenticationOptions.builder()
				.role(…).credential(GoogleCredentials.getApplicationDefault()).build();

		GcpIamCredentialsAuthentication authentication = new GcpIamCredentialsAuthentication(options,
				restOperations());
    }

    // …
}

GcpIamCredentialsAuthenticationOptions 需要 Google Cloud Java SDK 依赖项 (com.google.cloud:google-cloud-iamcredentials),因为认证实现使用 Google API 进行凭据和 JWT 签名。

您可以通过 GcpIamCredentialsAuthenticationOptions 配置认证。

Google 凭据需要维护 Token 生命周期的 OAuth 2 Token。所有 API 都是同步的,因此 GcpIamCredentialsAuthentication 不支持响应式使用所需的 AuthenticationSteps
GcpIamCredentialsAuthentication 使用 IAM Credentials API,它是替代已弃用的 GcpIamAuthentication(使用已弃用的 IAM API)的方式。

另请参见

PCF 认证

pcf 认证后端允许 PCF 实例登录 Vault。它利用了 PCF 的应用程序和容器身份保障

PCF 认证使用实例密钥和证书创建一个由 Vault 验证的签名。如果签名匹配,并且潜在绑定的组织/空间/应用程序 ID 匹配,Vault 会颁发一个适当范围的 Token。

实例凭据可从 CF_INSTANCE_CERTCF_INSTANCE_KEY 变量指定的文件中获取。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        PcfAuthenticationOptions options = PcfAuthenticationOptions.builder()
                .role(…).build();

        PcfAuthentication authentication = new PcfAuthentication(options,
                restOperations());
    }

    // …
}

PcfAuthenticationOptions 需要 BouncyCastle 库来创建 RSA-PSS 签名。

您可以通过 PcfAuthenticationOptions 配置认证。

另请参见

TLS 证书认证

cert 认证后端允许使用 SSL/TLS 客户端证书进行认证,这些证书可以由 CA 签名或自签名。

要启用 cert 认证,您需要

  1. 使用 SSL,请参见 [vault.client-ssl]

  2. 配置一个包含客户端证书和私钥的 Java Keystore

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        ClientCertificateAuthenticationOptions options = ClientCertificateAuthenticationOptions.builder()
                .path(…).build();

        return new ClientCertificateAuthentication(options, restOperations());
    }

    // …
}

Cubbyhole 认证

Cubbyhole 认证使用 Vault 原语提供安全的认证工作流。Cubbyhole 认证使用 Tokens 作为主要的登录方法。一个临时 Token 用于从 Vault 的 Cubbyhole Secret 后端获取第二个登录 VaultToken。登录 Token 通常具有更长的生命周期,用于与 Vault 交互。登录 Token 可以从包装的响应或从 data 部分中获取。

创建包装的 Token

Response Wrapping 创建 Token 需要 Vault 0.6.0 或更高版本。
示例 3. 创建和存储 Token
$ vault token-create -wrap-ttl="10m"
Key                            Value
---                            -----
wrapping_token:                397ccb93-ff6c-b17b-9389-380b01ca2645
wrapping_token_ttl:            0h10m0s
wrapping_token_creation_time:  2016-09-18 20:29:48.652957077 +0200 CEST
wrapped_accessor:              46b6aebb-187f-932a-26d7-4f3d86a68319
示例 4. 包装 Token 响应的使用
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
                .builder()
                .initialToken(VaultToken.of("…"))
                .wrapped()
                .build();

        return new CubbyholeAuthentication(options, restOperations());
    }

    // …
}

使用存储的 Token

示例 5. 创建和存储 Token
$ vault token create
Key                    Value
---                    -----
token                  f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
token_accessor         4eee9bd9-81bb-06d6-af01-723c54a72148
token_duration         0s
token_renewable        false
token_policies         [root]

$ vault token create -use-limit=2 -orphan -no-default-policy -policy=none
Key                    Value
---                    -----
token                  895cb88b-aef4-0e33-ba65-d50007290780
token_accessor         e84b661c-8aa8-2286-b788-f258f30c8325
token_duration         0s
token_renewable        false
token_policies         [none]

$ export VAULT_TOKEN=895cb88b-aef4-0e33-ba65-d50007290780
$ vault write cubbyhole/token token=f9e30681-d46a-cdaf-aaa0-2ae0a9ad0819
示例 6. 存储 Token 响应的使用
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        CubbyholeAuthenticationOptions options = CubbyholeAuthenticationOptions
                .builder()
                .initialToken(VaultToken.of("…"))
                .path("cubbyhole/token")
                .build();

        return new CubbyholeAuthentication(options, restOperations());
    }

    // …
}

剩余 TTL/可续期性

从 Cubbyhole 获取的、与非零 TTL 相关的 Token 会在 Token 创建时开始计算其 TTL。该时间不一定与应用程序启动时间相同。为了补偿初始延迟,Cubbyhole 认证会对其与非零 TTL 相关的 Token 执行自查询,以检索剩余的 TTL。Cubbyhole 认证不会对没有 TTL 的包装 Token 执行自查询,因为零 TTL 表示没有关联的 TTL。

非包装 Token 仅通过检索 Token 本身无法提供关于可续期性和 TTL 的详细信息。自查询将查找可续期性和剩余 TTL。

另请参见

JWT 认证

配置 JWT 认证需要提供 Token 或 JWT Supplier。您可以通过 JwtAuthenticationOptions 配置认证。

在 Vault 端,您可以通过启用 JWT 认证后端并创建一个角色来配置 JWT 后端。您可以使用 oidc_discovery_urljwks_urljwt_validation_pubkeys 来配置 JWT 后端。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        JwtAuthenticationOptions options = JwtAuthenticationOptions.builder()
                .role(…).jwt(…).path(…).build();

        return new JwtAuthentication(options, restOperations());
    }

    // …
}

另请参见

Kubernetes 认证

Vault 自 0.8.3 版本起支持使用 Kubernetes Token 的 kubernetes 认证。

使用 Kubernetes 认证需要一个 Kubernetes Service Account Token,通常挂载在 /var/run/secrets/kubernetes.io/serviceaccount/token。该文件包含读取并发送到 Vault 的 Token。Vault 在登录期间使用 Kubernetes 的 API 验证其有效性。

配置 Kubernetes 认证至少需要提供角色名

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        KubernetesAuthenticationOptions options = KubernetesAuthenticationOptions.builder()
                .role(…).jwtSupplier(…).build();

        return new KubernetesAuthentication(options, restOperations());
    }

    // …
}

您可以通过 KubernetesAuthenticationOptions 配置认证。

另请参见

用户名/密码认证

用户名/密码通常是终端用户认证方案。多种 Vault 认证后端支持使用用户名和密码

  • 用户名和密码 (userpass)

  • LDAP (ldap)

  • Okta (okta, 额外支持基于时间的一次性 Token)

  • RADIUS (radius)

UserPasswordAuthenticationOptions 可用于上述所有认证后端,因为登录 API 在所有机制中都相似。请确保在配置 UserPasswordAuthenticationOptions 时使用适当的认证挂载路径。

示例 7. 配置 UserPasswordAuthentication
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

        UserPasswordAuthenticationOptions options = UserPasswordAuthenticationOptions.builder()
                .username(…).password(…).build();

        return new UserPasswordAuthentication(options, restOperations());
    }

    // …
}

另请参见

认证步骤

ClientAuthentication 对象描述认证流程并执行实际的认证步骤。预组成的认证易于使用,并且配置与同步执行紧密绑定。

认证方法的组合和常见步骤的重用,例如将登录负载发送到 Vault 或从 HTTP 源检索认证输入,不适用于 ClientAuthentication 对象。

认证步骤提供了常见认证活动的可重用性。通过 AuthenticationSteps 创建的步骤以函数式风格描述了认证流程,将实际的认证执行留给了特定的执行器。

示例 8. 存储的 Token 认证流程。
AuthenticationSteps.just(VaultToken.of(…));                              (1)
1 仅使用一个 VaultToken 创建 AuthenticationSteps

单步认证流程可以从单个输入创建。声明多个认证步骤的流程始于一个 SupplierHttpRequest,它们提供一个认证状态对象,该对象可用于映射或发送到 Vault 进行登录。

示例 9. AppRole 认证流程
AuthenticationSteps.fromSupplier(                                       (1)

    () -> getAppRoleLogin(options.getRoleId(), options.getSecretId()))  (2)

    .login("auth/{mount}/login", options.getPath());                    (3)
1 开始声明接受 Supplier<T>AuthenticationSteps。状态对象的类型取决于 Supplier 的响应类型,可以在后续步骤中进行映射。
2 实际的 Supplier 实现。在这种情况下创建了一个 Map
3 通过将状态对象(Map)发送到 Vault 端点进行 Vault Token 创建来执行 Vault 登录。请注意,模板变量受 URL 转义的影响。

认证流程需要一个执行器来执行实际的登录。我们为不同的执行模型提供了两个执行器

  • AuthenticationStepsExecutor 作为同步 ClientAuthentication 的直接替代。

  • AuthenticationStepsOperator 用于响应式执行。

许多 ClientAuthentication 提供了静态工厂方法来为其特定于认证的选项创建 AuthenticationSteps

示例 10. 同步 AuthenticationSteps 执行
CubbyholeAuthenticationOptions options = …
RestOperations restOperations = …

AuthenticationSteps steps = CubbyholeAuthentication.createAuthenticationSteps(options);

AuthenticationStepsExecutor executor = new AuthenticationStepsExecutor(steps, restOperations);

VaultToken token = executor.login();

Token 生命周期

Vault 的 Token 可以关联一个存活时间 (TTL)。通过认证方法获得的 Token 旨在只要会话处于活动状态就使用,并且不应在应用程序活动期间过期。

Spring Vault 提供了 LifecycleAwareSessionManager 会话管理器,它可以续期 Token 直到其达到最终 TTL,然后执行另一次登录以获取与会话关联的下一个 Token。

根据认证方法,登录可以创建两种类型的 Token

  • VaultToken:封装实际 Token 的通用 Token。

  • LoginToken:与可续期性/TTL 相关的 Token。

TokenAuthentication 这样的认证方法只创建一个不包含任何可续期性/TTL 详细信息的 VaultToken。如果启用了自查询,LifecycleAwareSessionManager 将对 Token 运行自查询以从 Vault 获取可续期性和 TTL。如果启用了自查询,VaultToken 会定期续期。请注意,VaultToken 从不被撤销,只有 LoginToken 会被撤销。

直接创建 LoginToken 的认证方法(所有基于登录的认证方法)已经提供了设置 Token 续期所需的所有详细信息。如果会话管理器关闭,LifecycleAwareSessionManager 会撤销从登录获得的 Token。