身份验证方法

不同的组织对安全和身份验证有不同的需求。Vault 通过提供多种身份验证方法来满足这种需求。Spring Vault 支持多种身份验证机制。

外部化登录凭据

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

向任何一方泄露登录凭据都允许登录 Vault 并访问底层角色允许的机密信息。选择合适的客户端身份验证并将凭据注入应用程序需要进行风险评估。

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

令牌身份验证

令牌是 Vault 中身份验证的核心方法。令牌身份验证需要提供一个静态令牌。

令牌身份验证是默认的身份验证方法。如果令牌泄露给意外方,则该方将获得访问 Vault 的权限,并可以访问目标客户端的机密信息。

通常,令牌身份验证用于令牌在外部创建和更新的场景(例如 HashiCorp Vault 服务代理)。根据实际设置,您可能希望或不希望进行令牌续期和撤销。有关 TTL 和令牌撤销的详细信息,请参阅 LifecycleAwareSessionManager

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

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

    // …
}

另请参阅

AppId 身份验证

Vault 已弃用 AppId 身份验证。请改用 AppRole 身份验证

Vault 支持 AppId 身份验证,该身份验证由两个难以猜测的令牌组成。AppId 默认设置为 spring.application.name,该名称是静态配置的。第二个令牌是 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 进行身份验证以获取令牌时,它都会通过调用 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 身份验证由两个难以猜测(秘密)的令牌组成:RoleId 和 SecretId。

Spring Vault 通过仅提供 RoleId 或与提供的 SecretId 一起提供并从 Vault 中获取 RoleId/SecretId 来支持 AppRole 身份验证(带响应解包的推送和拉取模式)。

@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 还支持完整的拉取模式:如果未提供 RoleId 和 SecretId,Spring Vault 将使用角色名称和初始令牌检索它们。初始令牌可能与 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 令牌。与大多数 Vault 身份验证后端不同,此后端不需要首先部署或预配安全敏感的凭据(令牌、用户名/密码、客户端证书等)。相反,它将 AWS 视为可信第三方,并使用唯一表示每个 EC2 实例的加密签名动态元数据信息。

@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

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

    // …
}

AWS-EC2 身份验证默认启用随机数以遵循“首次信任”(TOFU)原则。任何获得 PKCS#7 身份元数据访问权限的意外方都可以对 Vault 进行身份验证。

在第一次登录期间,Spring Vault 生成一个随机数,该随机数与实例 ID 存储在身份验证后端中。重新身份验证需要发送相同的随机数。任何其他方都没有随机数,并且可以在 Vault 中引发警报以供进一步调查。

随机数保存在内存中,并在应用程序重新启动时丢失。

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

AWS-IAM 身份验证

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

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

IAM 凭证可以从运行时环境获取,也可以从外部提供。像 AWS-EC2、Lambda 和 ECS 这样的运行时环境,如果分配了 IAM 主体,则不需要客户端特定的凭证配置,而是可以从其元数据源获取这些凭证。

@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 令牌。与大多数 Vault 身份验证后端不同,此后端不需要首先部署或预配安全敏感的凭证(令牌、用户名/密码、客户端证书等)。相反,它将 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 令牌 (JWT) 形式的签名。Compute Engine 实例的 JWT 是使用 实例标识 从 GCE 元数据服务获取的。此 API 创建一个 JSON Web 令牌,可用于确认实例身份。

与大多数 Vault 身份验证后端不同,此后端不需要首先部署或预配安全敏感的凭证(令牌、用户名/密码、客户端证书等)。相反,它将 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 令牌 (JWT) 形式的签名。服务帐户的 JWT 是通过调用 GCP IAM 的 projects.serviceAccounts.signJwt API 获取的。调用方对 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 凭证需要一个维护令牌生命周期的 OAuth 2 令牌。所有 API 都是同步的,因此,GcpIamCredentialsAuthentication 不支持 AuthenticationSteps,而这对于响应式使用是必需的。
GcpIamCredentialsAuthentication 使用 IAM 凭证 API,并且是使用已弃用的 IAM API 的已弃用 GcpIamAuthentication 的替代方案。

另请参阅

PCF 身份验证

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

PCF 身份验证使用实例密钥和证书来创建签名,该签名由 Vault 验证。如果签名匹配,并且可能绑定的组织/空间/应用程序 ID 匹配,则 Vault 会发出一个范围适当的令牌。

实例凭证可从 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 身份验证使用令牌作为主要登录方法。一个短暂的令牌用于从 Vault 的 Cubbyhole 密钥后端获取第二个登录 VaultToken。登录令牌通常具有更长的生命周期,并用于与 Vault 交互。登录令牌可以从包装的响应中检索,也可以从 data 部分检索。

创建包装的令牌

用于令牌创建的响应包装需要 Vault 0.6.0 或更高版本。
示例 3. 创建和存储令牌
$ 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. 包装的令牌响应用法
@Configuration
class AppConfig extends AbstractVaultConfiguration {

    // …

    @Override
    public ClientAuthentication clientAuthentication() {

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

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

    // …
}

使用存储的令牌

示例 5. 创建和存储令牌
$ 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. 存储的令牌响应用法
@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/可续期

从与非零 TTL 关联的 Cubbyhole 中检索到的令牌在其创建时开始其 TTL。该时间不一定与应用程序启动时间相同。为了弥补初始延迟,Cubbyhole 身份验证会对与非零 TTL 关联的令牌执行自我查找以检索剩余 TTL。Cubbyhole 身份验证不会对没有 TTL 的包装令牌执行自我查找,因为零 TTL 表示没有关联的 TTL。

非包装令牌仅通过检索令牌不提供有关可续期和 TTL 的详细信息。自我查找将查找可续期和剩余 TTL。

另请参阅

JWT 身份验证

配置 JWT 身份验证需要令牌或 JWT 提供程序。您可以通过 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 令牌的 kubernetes 基于身份验证。

使用 Kubernetes 身份验证需要 Kubernetes 服务帐户令牌,通常安装在 /var/run/secrets/kubernetes.io/serviceaccount/token。该文件包含读取并发送到 Vault 的令牌。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,还支持基于时间的一次性令牌)

  • 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. 存储的令牌身份验证流程。
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 令牌创建来执行 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();

令牌生命周期

Vault 的令牌可以与生存时间相关联。通过身份验证方法获得的令牌旨在在会话处于活动状态时使用,并且在应用程序处于活动状态时不应过期。

Spring Vault 提供了一个名为 LifecycleAwareSessionManager 的会话管理器,它可以持续续期令牌,直到令牌达到其最终 TTL(生存时间),然后执行另一次登录以获取与会话关联的下一个令牌。

根据身份验证方法的不同,登录可以创建两种令牌。

某些身份验证方法,例如 TokenAuthentication,只会创建一个不包含任何续期/TTL 信息的 VaultTokenLifecycleAwareSessionManager 将对令牌执行自我查找以从 Vault 中检索续期信息和 TTL。如果启用了自我查找,则会定期续期 VaultToken。请注意,VaultToken 永远不会被撤销,只有 LoginToken 可以被撤销。

直接创建 LoginToken 的身份验证方法(所有基于登录的身份验证方法)都提供了设置令牌续期的所有必要详细信息。如果会话管理器关闭,则从登录获得的令牌将由 LifecycleAwareSessionManager 撤销。