-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
297 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 changes: 19 additions & 0 deletions
19
website/source/docs/agent/autoauth/methods/approle.html.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |