身份验证
Vault 中的身份验证是根据内部或外部系统验证用户或机器提供的信息的过程。 Vault 支持多种身份验证方法,包括 GitHub、LDAP、AppRole 等。每个身份验证方法都有对应的使用场景。
在客户端可以与 Vault 交互之前,必须先使用相关身份验证方法进行身份验证。身份验证后,会生成令牌。该令牌在概念上类似于网站上的会话 ID。令牌可能具有附加的策略,该策略在身份验证时被映射。随后的策略概念章节中会详细描述此过程。
验证方法
Vault 支持多种验证方法。有些是面向人类用户的,而其他的则是面向验证机器身份的。大多数身份验证后端必须在使用前被启用。要启用一个身份验证方法可以用如下命令:
$ vault write sys/auth/my-auth type=userpass
该命令在路径 my-auth
上启用了 userpass
认证方法。通常来说我们会看到认证方法的名字就是启用它的路径名,但这并不是强制的。
想要了解某个路径上启用的身份验证方法的详细信息,可以使用内建的 path-help
命令:
$ vault path-help auth/my-auth
Vault 支持同时使用多种身份验证方法,我们甚至可以在不同路径上挂载相同类型的身份验证方法。只需进行一次身份验证即可访问 Vault,目前无法强制用户必须通过多种身份验证方法才能获得访问权限,尽管某些后端确实支持多因素认证。
令牌
随后会有专门讲述令牌的章节,但在这里我们需要了解的是,身份验证的工作原理就是验证我们的身份,然后生成与该身份相关联的令牌。
举例来说,即使我们可以使用 GitHub 之类的工具进行身份验证,Vault 也会生成一个唯一的访问令牌供我们将来使用。 命令行工具会自动将此令牌附加到请求,但如果我们使用的是 API,则必须手动执行此操作。
任何后端对应的身份验证方式创建的令牌都可以与所有的令牌命令一起使用,例如创建新的子令牌、撤销令牌和更新令牌。这一切都将在随后的令牌概念章节中进行介绍。
执行身份验证
使用命令行
要使用命令行进行身份验证,可以使用 vault login
命令。该命令支持多种内建的验证方式。举一个使用 GitHub 的例子:
$ vault login -method=github token=<token>
身份验证完成后,我们就完成了对 Vault 的登录。命令还将输出您的令牌。此令牌用于吊销和续订操作。因为此时用户已经登录,所以令牌主要用来续订租约。
如果想要确定该验证方式需要提供哪些参数,可以只添加 -method
标记,命令会显示帮助信息。
如果使用的验证方式不支持命令行方式,那么必须使用 API。
使用 API
API 认证一般用于验证机器身份。每个验证方法都实现了自己的登录端点。使用 vault path-help
命令可以查找正确的端点。
举例来说,GitHub 登录端点位于 auth/github/login
。为了确定所需的参数,可以使用命令 vault path-help auth/github/login
。
身份租约
和 Vault 管理的机密一样,身份也有与之相关的租约。这意味着我们必须在给定的租约到期后重新进行身份验证才能继续访问 Vault。
要设置与身份关联的租约,请参考所使用的特定身份验证方法的帮助信息。租约机制的具体实施方式取决于每个实现身份验证的后端。
就像机密一样,也可续约身份租约而不是重新验证身份。只需使用 vault token renew <token>
命令来续约与身份关联的令牌的租约。
样例代码
以下代码片段展示了如何续约身份令牌:
package main
import (
"context"
"fmt"
"log"
vault "github.com/hashicorp/vault/api"
auth "github.com/hashicorp/vault/api/auth/userpass"
)
// Once you've set the token for your Vault client, you will need to
// periodically renew its lease.
//
// A function like this should be run as a goroutine to avoid blocking.
//
// Production applications may also wish to be more tolerant of failures and
// retry rather than exiting.
//
// Additionally, enterprise Vault users should be aware that due to eventual
// consistency, the API may return unexpected errors when running Vault with
// performance standbys or performance replication, despite the client having
// a freshly renewed token. See https://www.vaultproject.io/docs/enterprise/consistency#vault-1-7-mitigations
// for several ways to mitigate this which are outside the scope of this code sample.
func renewToken(client *vault.Client) {
for {
vaultLoginResp, err := login(client)
if err != nil {
log.Fatalf("unable to authenticate to Vault: %v", err)
}
tokenErr := manageTokenLifecycle(client, vaultLoginResp)
if tokenErr != nil {
log.Fatalf("unable to start managing token lifecycle: %v", tokenErr)
}
}
}
// Starts token lifecycle management. Returns only fatal errors as errors,
// otherwise returns nil so we can attempt login again.
func manageTokenLifecycle(client *vault.Client, token *vault.Secret) error {
renew := token.Auth.Renewable // You may notice a different top-level field called Renewable. That one is used for dynamic secrets renewal, not token renewal.
if !renew {
log.Printf("Token is not configured to be renewable. Re-attempting login.")
return nil
}
watcher, err := client.NewLifetimeWatcher(&vault.LifetimeWatcherInput{
Secret: token,
Increment: 3600, // Learn more about this optional value in https://www.vaultproject.io/docs/concepts/lease#lease-durations-and-renewal
})
if err != nil {
return fmt.Errorf("unable to initialize new lifetime watcher for renewing auth token: %w", err)
}
go watcher.Start()
defer watcher.Stop()
for {
select {
// `DoneCh` will return if renewal fails, or if the remaining lease
// duration is under a built-in threshold and either renewing is not
// extending it or renewing is disabled. In any case, the caller
// needs to attempt to log in again.
case err := <-watcher.DoneCh():
if err != nil {
log.Printf("Failed to renew token: %v. Re-attempting login.", err)
return nil
}
// This occurs once the token has reached max TTL.
log.Printf("Token can no longer be renewed. Re-attempting login.")
return nil
// Successfully completed renewal
case renewal := <-watcher.RenewCh():
log.Printf("Successfully renewed: %#v", renewal)
}
}
}
func login(client *vault.Client) (*vault.Secret, error) {
// WARNING: A plaintext password like this is obviously insecure.
// See the hashicorp/vault-examples repo for full examples of how to securely
// log in to Vault using various auth methods. This function is just
// demonstrating the basic idea that a *vault.Secret is returned by
// the login call.
userpassAuth, err := auth.NewUserpassAuth("my-user", &auth.Password{FromString: "my-password"})
if err != nil {
return nil, fmt.Errorf("unable to initialize userpass auth method: %w", err)
}
authInfo, err := client.Auth().Login(context.TODO(), userpassAuth)
if err != nil {
return nil, fmt.Errorf("unable to login to userpass auth method: %w", err)
}
if authInfo == nil {
return nil, fmt.Errorf("no auth info was returned after login")
}
return authInfo, nil
}