Skip to content

Commit

Permalink
Add AppRole connection support (#20)
Browse files Browse the repository at this point in the history
* Add AppRole connection support
  • Loading branch information
tbeaugrand authored Sep 21, 2022
1 parent a2d3b21 commit 7f08a56
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 77 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ check: setup
setup: ./bin/golangci-lint

./bin/golangci-lint:
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.45.2
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s v1.49.0
19 changes: 5 additions & 14 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ require (
github.com/hashicorp/vault/api v1.5.0
github.com/mitchellh/go-homedir v1.1.0
github.com/ory/dockertest v3.3.5+incompatible
github.com/ory/dockertest/v3 v3.9.1
github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.1
)
Expand All @@ -19,18 +18,13 @@ require (
github.com/armon/go-radix v1.0.0 // indirect
github.com/cenkalti/backoff v2.2.1+incompatible // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
github.com/containerd/continuity v0.3.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/cli v20.10.17+incompatible // indirect
github.com/docker/docker v20.10.17+incompatible // indirect
github.com/docker/go-connections v0.4.0 // indirect
github.com/docker/go-units v0.4.0 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand All @@ -50,14 +44,13 @@ require (
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/hashicorp/vault/sdk v0.4.1 // indirect
github.com/hashicorp/yamux v0.0.0-20211028200310-0bc27b27de87 // indirect
github.com/imdario/mergo v0.3.13 // indirect
github.com/lib/pq v0.0.0-20180327071824-d34b9ff171c2 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.0.2 // indirect
Expand All @@ -66,20 +59,18 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/ryanuber/go-glob v1.0.0 // indirect
github.com/sirupsen/logrus v1.8.1 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20220427172511-eb4f295cb31f // indirect
golang.org/x/net v0.0.0-20220615171555-694bf12d69de // indirect
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c // indirect
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/time v0.0.0-20220411224347-583f2d630306 // indirect
golang.org/x/tools v0.1.12 // indirect
google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e // indirect
google.golang.org/grpc v1.46.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0 // indirect
gotest.tools v2.2.0+incompatible // indirect
)
60 changes: 9 additions & 51 deletions go.sum

Large diffs are not rendered by default.

30 changes: 27 additions & 3 deletions internal/libvault/vault.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package libvault

import (
"io/ioutil"
"os"
"path/filepath"

vault "github.com/hashicorp/vault/api"
"github.com/mitchellh/go-homedir"

vault "github.com/hashicorp/vault/api"
"github.com/pkg/errors"
)

Expand All @@ -20,7 +20,7 @@ func CreateClient() (*vault.Client, error) {
}
vaultTokenFile := filepath.Join(homePath, ".vault-token")
if _, err = os.Stat(vaultTokenFile); err == nil {
content, err := ioutil.ReadFile(vaultTokenFile)
content, err := os.ReadFile(vaultTokenFile)
if err != nil {
return nil, errors.Wrap(err, "unable to read .vault-token file")
}
Expand All @@ -44,3 +44,27 @@ func CreateClient() (*vault.Client, error) {

return client, nil
}

func CreateClientWithAppRole(roleID, secretID string) (*vault.Client, error) {
client, err := vault.NewClient(nil)
if err != nil {
return nil, errors.Wrap(err, "Unable to initialize Vault client")
}

data := map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
}

resp, err := client.Logical().Write("auth/approle/login", data)
if err != nil {
return nil, errors.Wrap(err, "Unable to generate token")
}

if resp.Auth == nil {
return nil, errors.New("no authentication info returned")
}

client.SetToken(resp.Auth.ClientToken)
return client, nil
}
11 changes: 11 additions & 0 deletions vaultv1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,17 @@ func CreateClient() (*Client, error) {
}, nil
}

func CreateClientWithAppRole(roleID, secretID string) (*Client, error) {
client, err := libvault.CreateClientWithAppRole(roleID, secretID)
if err != nil {
return nil, errors.Wrapf(err, "")
}

return &Client{
Client: client,
}, nil
}

func (vc *Client) ListSecretPath(path string) ([]string, error) {
s, err := vc.Client.Logical().List(path)
if err != nil {
Expand Down
60 changes: 60 additions & 0 deletions vaultv1/v1_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,63 @@ func TestListSecret(t *testing.T) {
_, err = client.ListSecretPath("kv-v1-2/not_exist")
require.Error(t, err, "The path \"kv-v1-2/not_exist\" does not exist")
}

func TestWithAppRole(t *testing.T) {
os.Clearenv()
os.Setenv("VAULT_ADDR", v1Endpoint)
os.Setenv("VAULT_TOKEN", "root")

vc, err := vaultv1.CreateClient()
require.NoError(t, err)
err = vc.Client.Sys().Mount("kv-v1-3", &vault.MountInput{
Type: "kv",
Options: map[string]string{"version": "1"},
})
require.NoError(t, err, "This should not fail")

addSecret(t, vc, "kv-v1-3/approle", map[string]interface{}{"roleID": "xxxxxx"})

roleID, secretID := setupAppRole(t, vc)
appRoleClient, err := vaultv1.CreateClientWithAppRole(fmt.Sprint(roleID), fmt.Sprint(secretID))
require.NoError(t, err)

secret, err := appRoleClient.GetSecret("kv-v1-3/approle")
require.NoError(t, err)

require.Equal(t, "xxxxxx", secret["roleID"])
}

func setupAppRole(t *testing.T, vc *vaultv1.Client) (string, string) {
// Enable approle
err := vc.Client.Sys().EnableAuthWithOptions("approle/", &vault.EnableAuthOptions{
Type: "approle",
})
require.NoError(t, err)

// Create a policy to allow the approle to do whatever
err = vc.Client.Sys().PutPolicy("unittest", `
path "*" {
capabilities = ["create", "read", "list", "update", "delete"]
}
`)
require.NoError(t, err)

// Create role
_, err = vc.Client.Logical().Write("auth/approle/role/roletest", map[string]interface{}{
"period": "5m",
"policies": []string{"unittest"},
})
require.NoError(t, err)

// Get role_id
resp, err := vc.Client.Logical().Read("auth/approle/role/roletest/role-id")
require.NoError(t, err)
roleID := resp.Data["role_id"]

// Get secret_id
resp, err = vc.Client.Logical().Write("auth/approle/role/roletest/secret-id", map[string]interface{}{})
require.NoError(t, err)
secretID := resp.Data["secret_id"]

return fmt.Sprint(roleID), fmt.Sprint(secretID)
}
11 changes: 11 additions & 0 deletions vaultv2/v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ func CreateClient() (*Client, error) {
}, nil
}

func CreateClientWithAppRole(roleID, secretID string) (*Client, error) {
client, err := libvault.CreateClientWithAppRole(roleID, secretID)
if err != nil {
return nil, errors.Wrapf(err, "")
}

return &Client{
Client: client,
}, nil
}

func (vc *Client) ReadSecret(path string, field string) (string, error) {
secret, err := vc.GetSecret(path)
if err != nil {
Expand Down
72 changes: 64 additions & 8 deletions vaultv2/v2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,16 @@ package vaultv2

import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"testing"

vault "github.com/hashicorp/vault/api"

"github.com/mirakl/lib-vault/v2/internal/libvault"
"github.com/mitchellh/go-homedir"
"github.com/ory/dockertest/v3"
"github.com/ory/dockertest"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -70,11 +71,7 @@ func TestCreateClientTokenFromFile(t *testing.T) {
_, err := CreateClient()
require.Error(t, err, "Couldn't find neither $VAULT_TOKEN nor ~/.vault-token file")

tmpdir, err := ioutil.TempDir("", "vaultread_test")
if err != nil {
t.Fatalf("unable to create tmpdir %q : %q", tmpdir, err)
}

tmpdir := os.TempDir()
t.Cleanup(func() {
os.RemoveAll(tmpdir)
})
Expand All @@ -85,7 +82,7 @@ func TestCreateClientTokenFromFile(t *testing.T) {
t.Fatalf("error getting user's home directory %q : %q", homePath, err)
}
tokenPath := filepath.Join(homePath, ".vault-token")
if err := ioutil.WriteFile(tokenPath, []byte("root"), 0600); err != nil {
if err := os.WriteFile(tokenPath, []byte("root"), 0600); err != nil {
t.Fatalf("unable to write file : %q", tokenPath)
}

Expand Down Expand Up @@ -174,3 +171,62 @@ func TestGetSecretKvV2(t *testing.T) {
require.Equal(t, "bar2", s["secret2"])
require.Equal(t, "bar3", s["secret3"])
}

func TestWithAppRoleV2(t *testing.T) {
os.Clearenv()
os.Setenv("VAULT_ADDR", v2Endpoint)
os.Setenv("VAULT_TOKEN", "root")

vc, err := CreateClient()
require.NoError(t, err)

addSecret(t, vc, "secret/data/approle", map[string]interface{}{
"data": map[string]interface{}{
"roleID": "xxxxxx",
},
})

roleID, secretID := setupAppRole(t, vc)
appRoleClient, err := CreateClientWithAppRole(fmt.Sprint(roleID), fmt.Sprint(secretID))
require.NoError(t, err)

secret, err := appRoleClient.GetSecret("secret/approle")
require.NoError(t, err)

require.Equal(t, "xxxxxx", secret["roleID"])
}

func setupAppRole(t *testing.T, vc *Client) (string, string) {
// Enable approle
err := vc.Client.Sys().EnableAuthWithOptions("approle/", &vault.EnableAuthOptions{
Type: "approle",
})
require.NoError(t, err)

// Create a policy to allow the approle to do whatever
err = vc.Client.Sys().PutPolicy("unittest", `
path "*" {
capabilities = ["create", "read", "list", "update", "delete"]
}
`)
require.NoError(t, err)

// Create role
_, err = vc.Client.Logical().Write("auth/approle/role/roletest", map[string]interface{}{
"period": "5m",
"policies": []string{"unittest"},
})
require.NoError(t, err)

// Get role_id
resp, err := vc.Client.Logical().Read("auth/approle/role/roletest/role-id")
require.NoError(t, err)
roleID := resp.Data["role_id"]

// Get secret_id
resp, err = vc.Client.Logical().Write("auth/approle/role/roletest/secret-id", map[string]interface{}{})
require.NoError(t, err)
secretID := resp.Data["secret_id"]

return fmt.Sprint(roleID), fmt.Sprint(secretID)
}

0 comments on commit 7f08a56

Please sign in to comment.