Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API helper for renewing a secret #2886

Merged
merged 25 commits into from
Jul 11, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
96 changes: 96 additions & 0 deletions api/api_integration_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package api_test

import (
"database/sql"
"fmt"
"testing"

"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/logical/pki"
"github.com/hashicorp/vault/builtin/logical/transit"
"github.com/hashicorp/vault/logical"
"github.com/hashicorp/vault/vault"

vaulthttp "github.com/hashicorp/vault/http"
logxi "github.com/mgutz/logxi/v1"
dockertest "gopkg.in/ory-am/dockertest.v3"
)

var testVaultServerDefaultBackends = map[string]logical.Factory{
"transit": transit.Factory,
"pki": pki.Factory,
}

func testVaultServer(t testing.TB) (*api.Client, func()) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd like to discuss pulling the functions in this file into a testing package which other Go tools that leverage Vault can use to stand up a Vault cluster and client in tests.

Copy link
Member

@ryanuber ryanuber Jun 26, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm +1 for this, I would definitely use it. Consul has a similar thing in its testutil package. I'm not married to the testutil name but a dedicated package with all of the test helpers exported (public) would be huge. For reference, the consul test server stuff is here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also vote for tackling this in a separate PR though to minimize scope.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Vault already has a bunch of these. I would suggest not adding such a PR right now as you're likely to duplicate work.

return testVaultServerBackends(t, testVaultServerDefaultBackends)
}

func testVaultServerBackends(t testing.TB, backends map[string]logical.Factory) (*api.Client, func()) {
coreConfig := &vault.CoreConfig{
DisableMlock: true,
DisableCache: true,
Logger: logxi.NullLog,
LogicalBackends: backends,
}

cluster := vault.NewTestCluster(t, coreConfig, true)
cluster.StartListeners()
for _, core := range cluster.Cores {
core.Handler.Handle("/", vaulthttp.Handler(core.Core))
}

// make it easy to get access to the active
core := cluster.Cores[0].Core
vault.TestWaitActive(t, core)

// Grab the root token
rootToken := cluster.Cores[0].Root

client := cluster.Cores[0].Client
client.SetToken(rootToken)

// Sanity check
secret, err := client.Auth().Token().LookupSelf()
if err != nil {
t.Fatal(err)
}
if secret == nil || secret.Data["id"].(string) != rootToken {
t.Fatalf("token mismatch: %#v vs %q", secret, rootToken)
}
return client, func() { defer cluster.CloseListeners() }
}

// testPostgresDB creates a testing postgres database in a Docker container,
// returning the connection URL and the associated closer function.
func testPostgresDB(t testing.TB) (string, func()) {
pool, err := dockertest.NewPool("")
if err != nil {
t.Fatalf("postgresdb: failed to connect to docker: %s", err)
}

resource, err := pool.Run("postgres", "latest", []string{
"POSTGRES_PASSWORD=secret",
"POSTGRES_DB=database",
})
if err != nil {
t.Fatalf("postgresdb: could not start container: %s", err)
}

addr := fmt.Sprintf("postgres://postgres:secret@localhost:%s/database?sslmode=disable", resource.GetPort("5432/tcp"))

if err := pool.Retry(func() error {
db, err := sql.Open("postgres", addr)
if err != nil {
return err
}
return db.Ping()
}); err != nil {
t.Fatalf("postgresdb: could not connect: %s", err)
}

return addr, func() {
if err := pool.Purge(resource); err != nil {
t.Fatalf("postgresdb: failed to cleanup container: %s", err)
}
}
}
20 changes: 20 additions & 0 deletions api/auth_token.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,26 @@ func (c *TokenAuth) RenewSelf(increment int) (*Secret, error) {
return ParseSecret(resp.Body)
}

// RenewTokenAsSelf behaves like renew-self, but authenticates using a provided
// token instead of the token attached to the client.
func (c *TokenAuth) RenewTokenAsSelf(token string, increment int) (*Secret, error) {
r := c.c.NewRequest("PUT", "/v1/auth/token/renew-self")
r.ClientToken = token
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will make the client not thread safe, since any call to RenewTokenAsSelf will change the token that is being used in another thread. Might be worth copying the underlying client to avoid the global modification.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copying the client is really challenging due to the way we setup the underlying TLS and connection stuff 😦

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also note this is setting the token on the request, not the client (it's r.ClientToken, not c.Token). Since each call to NewRequest makes a new struct, I don't see this actually mutating state. Am I misinterpreting the way NewRequest works? It does not seem to modify the actual client.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sethvargo Yes, I believe that should be fine. FYI there is a tls.Config.Clone() method (https://golang.org/pkg/crypto/tls/#Config.Clone) but it's only semi-safe in that underlying pointers are copied as-is so you can't use that and then say modify the CA pools without affecting other clients.


body := map[string]interface{}{"increment": increment}
if err := r.SetJSONBody(body); err != nil {
return nil, err
}

resp, err := c.c.RawRequest(r)
if err != nil {
return nil, err
}
defer resp.Body.Close()

return ParseSecret(resp.Body)
}

// RevokeAccessor revokes a token associated with the given accessor
// along with all the child tokens.
func (c *TokenAuth) RevokeAccessor(accessor string) error {
Expand Down
Loading