mirror of
https://github.com/hashicorp/vault.git
synced 2026-05-04 12:01:23 +02:00
WIP Agent AppRole auto-auth (#5621)
This commit is contained in:
parent
c0b97de908
commit
e9d855217b
@ -17,6 +17,7 @@ import (
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/command/agent/auth"
|
||||
"github.com/hashicorp/vault/command/agent/auth/alicloud"
|
||||
"github.com/hashicorp/vault/command/agent/auth/approle"
|
||||
"github.com/hashicorp/vault/command/agent/auth/aws"
|
||||
"github.com/hashicorp/vault/command/agent/auth/azure"
|
||||
"github.com/hashicorp/vault/command/agent/auth/gcp"
|
||||
@ -296,6 +297,8 @@ func (c *AgentCommand) Run(args []string) int {
|
||||
method, err = jwt.NewJWTAuthMethod(authConfig)
|
||||
case "kubernetes":
|
||||
method, err = kubernetes.NewKubernetesAuthMethod(authConfig)
|
||||
case "approle":
|
||||
method, err = approle.NewApproleAuthMethod(authConfig)
|
||||
default:
|
||||
c.UI.Error(fmt.Sprintf("Unknown auth method %q", config.AutoAuth.Method.Type))
|
||||
return 1
|
||||
|
||||
186
command/agent/approle_end_to_end_test.go
Normal file
186
command/agent/approle_end_to_end_test.go
Normal file
@ -0,0 +1,186 @@
|
||||
package agent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
log "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/api"
|
||||
credAppRole "github.com/hashicorp/vault/builtin/credential/approle"
|
||||
"github.com/hashicorp/vault/command/agent/auth"
|
||||
agentapprole "github.com/hashicorp/vault/command/agent/auth/approle"
|
||||
"github.com/hashicorp/vault/command/agent/sink"
|
||||
"github.com/hashicorp/vault/command/agent/sink/file"
|
||||
"github.com/hashicorp/vault/helper/logging"
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/logical"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
func TestAppRoleEndToEnd(t *testing.T) {
|
||||
var err error
|
||||
logger := logging.NewVaultLogger(log.Trace)
|
||||
coreConfig := &vault.CoreConfig{
|
||||
DisableMlock: true,
|
||||
DisableCache: true,
|
||||
Logger: log.NewNullLogger(),
|
||||
CredentialBackends: map[string]logical.Factory{
|
||||
"approle": credAppRole.Factory,
|
||||
},
|
||||
}
|
||||
|
||||
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
cores := cluster.Cores
|
||||
|
||||
vault.TestWaitActive(t, cores[0].Core)
|
||||
|
||||
client := cores[0].Client
|
||||
|
||||
err = client.Sys().EnableAuthWithOptions("approle", &api.EnableAuthOptions{
|
||||
Type: "approle",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = client.Logical().Write("auth/approle/role/role1", map[string]interface{}{
|
||||
"bind_secret_id": "true",
|
||||
"period": "300",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
vaultResult, err := client.Logical().Write("auth/approle/role/role1/secret-id", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secretID := vaultResult.Data["secret_id"].(string)
|
||||
|
||||
vaultResult, err = client.Logical().Read("auth/approle/role/role1/role-id")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
roleID := vaultResult.Data["role_id"].(string)
|
||||
|
||||
rolef, err := ioutil.TempFile("", "auth.role-id.test.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
role := rolef.Name()
|
||||
defer rolef.Close()
|
||||
defer os.Remove(role)
|
||||
|
||||
t.Logf("input role_id_path: %s", role)
|
||||
if err := ioutil.WriteFile(role, []byte(roleID), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
logger.Trace("wrote test role-id", "path", role)
|
||||
}
|
||||
|
||||
secretf, err := ioutil.TempFile("", "auth.secret-id.test.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
secret := secretf.Name()
|
||||
defer secretf.Close()
|
||||
defer os.Remove(secret)
|
||||
|
||||
t.Logf("input secret_id_path: %s", secret)
|
||||
if err := ioutil.WriteFile(secret, []byte(secretID), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
logger.Trace("wrote test secret-id", "path", secret)
|
||||
}
|
||||
|
||||
// We close these right away because we're just basically testing
|
||||
// permissions and finding a usable file name
|
||||
ouf, err := ioutil.TempFile("", "auth.tokensink.test.")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
out := ouf.Name()
|
||||
ouf.Close()
|
||||
os.Remove(out)
|
||||
t.Logf("output: %s", out)
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(context.Background())
|
||||
timer := time.AfterFunc(30*time.Second, func() {
|
||||
cancelFunc()
|
||||
})
|
||||
defer timer.Stop()
|
||||
|
||||
am, err := agentapprole.NewApproleAuthMethod(&auth.AuthConfig{
|
||||
Logger: logger.Named("auth.approle"),
|
||||
MountPath: "auth/approle",
|
||||
Config: map[string]interface{}{
|
||||
"role_id_path": role,
|
||||
"secret_id_path": secret,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ahConfig := &auth.AuthHandlerConfig{
|
||||
Logger: logger.Named("auth.handler"),
|
||||
Client: client,
|
||||
}
|
||||
ah := auth.NewAuthHandler(ahConfig)
|
||||
go ah.Run(ctx, am)
|
||||
defer func() {
|
||||
<-ah.DoneCh
|
||||
}()
|
||||
|
||||
config := &sink.SinkConfig{
|
||||
Logger: logger.Named("sink.file"),
|
||||
Config: map[string]interface{}{
|
||||
"path": out,
|
||||
},
|
||||
}
|
||||
fs, err := file.NewFileSink(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
config.Sink = fs
|
||||
|
||||
ss := sink.NewSinkServer(&sink.SinkServerConfig{
|
||||
Logger: logger.Named("sink.server"),
|
||||
Client: client,
|
||||
})
|
||||
go ss.Run(ctx, ah.OutputCh, []*sink.SinkConfig{config})
|
||||
defer func() {
|
||||
<-ss.DoneCh
|
||||
}()
|
||||
|
||||
// This has to be after the other defers so it happens first
|
||||
defer cancelFunc()
|
||||
|
||||
// Check that no sink file exists
|
||||
_, err = os.Lstat(out)
|
||||
if err == nil {
|
||||
t.Fatal("expected err")
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
t.Fatal("expected notexist err")
|
||||
}
|
||||
|
||||
token, err := client.Logical().Write("auth/approle/login", map[string]interface{}{
|
||||
"role_id": roleID,
|
||||
"secret_id": secretID,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if token.Auth.ClientToken == "" {
|
||||
t.Fatalf("expected a successful login")
|
||||
}
|
||||
}
|
||||
89
command/agent/auth/approle/approle.go
Normal file
89
command/agent/auth/approle/approle.go
Normal file
@ -0,0 +1,89 @@
|
||||
package approle
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/hashicorp/vault/command/agent/auth"
|
||||
)
|
||||
|
||||
type approleMethod struct {
|
||||
logger hclog.Logger
|
||||
mountPath string
|
||||
|
||||
roleIDPath string
|
||||
secretIDPath string
|
||||
}
|
||||
|
||||
func NewApproleAuthMethod(conf *auth.AuthConfig) (auth.AuthMethod, error) {
|
||||
if conf == nil {
|
||||
return nil, errors.New("empty config")
|
||||
}
|
||||
if conf.Config == nil {
|
||||
return nil, errors.New("empty config data")
|
||||
}
|
||||
|
||||
a := &approleMethod{
|
||||
logger: conf.Logger,
|
||||
mountPath: conf.MountPath,
|
||||
}
|
||||
|
||||
roleIDPathRaw, ok := conf.Config["role_id_path"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing 'role_id_path' value")
|
||||
}
|
||||
a.roleIDPath, ok = roleIDPathRaw.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("could not convert 'role_id_path' config value to string")
|
||||
}
|
||||
if a.roleIDPath == "" {
|
||||
return nil, errors.New("'role_id_path' value is empty")
|
||||
}
|
||||
|
||||
secretIDPathRaw, ok := conf.Config["secret_id_path"]
|
||||
if !ok {
|
||||
return nil, errors.New("missing 'secret_id_path' value")
|
||||
}
|
||||
a.secretIDPath, ok = secretIDPathRaw.(string)
|
||||
if !ok {
|
||||
return nil, errors.New("could not convert 'secret_id_path' config value to string")
|
||||
}
|
||||
if a.secretIDPath == "" {
|
||||
return nil, errors.New("'secret_id_path' value is empty")
|
||||
}
|
||||
|
||||
return a, nil
|
||||
}
|
||||
|
||||
func (a *approleMethod) Authenticate(ctx context.Context, client *api.Client) (string, map[string]interface{}, error) {
|
||||
a.logger.Trace("beginning authentication")
|
||||
roleID, err := ioutil.ReadFile(a.roleIDPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
secretID, err := ioutil.ReadFile(a.secretIDPath)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s/login", a.mountPath), map[string]interface{}{
|
||||
"role_id": strings.TrimSpace(string(roleID)),
|
||||
"secret_id": strings.TrimSpace(string(secretID)),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *approleMethod) NewCreds() chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *approleMethod) CredSuccess() {
|
||||
}
|
||||
|
||||
func (a *approleMethod) Shutdown() {
|
||||
}
|
||||
19
website/source/docs/agent/autoauth/methods/approle.html.md
Normal file
19
website/source/docs/agent/autoauth/methods/approle.html.md
Normal file
@ -0,0 +1,19 @@
|
||||
---
|
||||
layout: "docs"
|
||||
page_title: "Vault Agent Auto-Auth AppRole Method"
|
||||
sidebar_title: "AppRole"
|
||||
sidebar_current: "docs-agent-autoauth-methods-approle"
|
||||
description: |-
|
||||
AppRole Method for Vault Agent Auto-Auth
|
||||
---
|
||||
|
||||
# Vault Agent Auto-Auth AppRole Method
|
||||
|
||||
The `approle` method reads in a role-id/secret-id from a files and sends it to the [AppRole Auth
|
||||
method](https://www.vaultproject.io/docs/auth/approle.html).
|
||||
|
||||
## Configuration
|
||||
|
||||
* `role_id_path` `(string: required)` - The path to the file with role-id
|
||||
|
||||
* `secret_id_path` `(string: required)` - The path to the file with secret-id
|
||||
Loading…
x
Reference in New Issue
Block a user