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

AWS upgrade role entries #7025

Merged
merged 58 commits into from
Jul 5, 2019
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
981f708
upgrade aws roles
mjarmy Jun 30, 2019
1ead348
test upgrade aws roles
mjarmy Jun 30, 2019
cccb56e
Initialize aws credential backend at mount time
mjarmy Jun 30, 2019
ce1dbe0
add a TODO
mjarmy Jun 30, 2019
593048f
create end-to-end test for builtin/credential/aws
mjarmy Jun 30, 2019
7e900b6
fix bug in initializer
mjarmy Jun 30, 2019
add4b06
improve comments
mjarmy Jun 30, 2019
24a10f5
add Initialize() to logical.Backend
mjarmy Jun 30, 2019
6abe0ee
use Initialize() in Core.enableCredentialInternal()
mjarmy Jun 30, 2019
a3d10a5
use InitializeRequest to call Initialize()
mjarmy Jul 1, 2019
ae25817
improve unit testing for framework.Backend
mjarmy Jul 1, 2019
274ed9d
call logical.Backend.Initialize() from all of the places that it need…
mjarmy Jul 1, 2019
397dd6f
implement backend.proto changes for logical.Backend.Initialize()
mjarmy Jul 1, 2019
e38ffab
persist current role storage version when upgrading aws roles
mjarmy Jul 1, 2019
f68254b
format comments correctly
mjarmy Jul 1, 2019
07e4722
improve comments
mjarmy Jul 1, 2019
7bca471
use postUnseal funcs to initialize backends
mjarmy Jul 1, 2019
bffa3f5
simplify test suite
mjarmy Jul 1, 2019
851610c
improve test suite
mjarmy Jul 1, 2019
67ac41c
merge from master
mjarmy Jul 2, 2019
4f36e95
simplify logic in aws role upgrade
mjarmy Jul 2, 2019
ccb86b5
simplify aws credential initialization logic
mjarmy Jul 2, 2019
1f95d4b
simplify logic in aws role upgrade
mjarmy Jul 2, 2019
bdef021
use the core's activeContext for initialization
mjarmy Jul 2, 2019
65df479
refactor builtin/plugin/Backend
mjarmy Jul 2, 2019
ce5e3cf
use a goroutine to upgrade the aws roles
mjarmy Jul 2, 2019
5401e63
misc improvements and cleanup
mjarmy Jul 2, 2019
e67f828
do not run AWS role upgrade on DR Secondary
mjarmy Jul 2, 2019
e574a14
always call logical.Backend.Initialize() when loading a plugin.
mjarmy Jul 2, 2019
dc7db93
improve comments
mjarmy Jul 2, 2019
9e74fb3
on standbys and DR secondaries we do not want to run any kind of upgr…
mjarmy Jul 2, 2019
208eb8d
merge from master
mjarmy Jul 3, 2019
11dbf68
fix awsVersion struct
mjarmy Jul 3, 2019
034791b
merge from master
mjarmy Jul 3, 2019
f95bb32
merge fixes to aws version
mjarmy Jul 3, 2019
5187bd2
clarify aws version upgrade
mjarmy Jul 3, 2019
c5e63e4
make the upgrade logic for aws auth more explicit
mjarmy Jul 3, 2019
c652d55
aws upgrade is now called from a switch
mjarmy Jul 3, 2019
0344a87
fix fallthrough bug
mjarmy Jul 3, 2019
52dae95
simplify logic
mjarmy Jul 3, 2019
c3bdfac
simplify logic
mjarmy Jul 3, 2019
94e707b
rename things
mjarmy Jul 3, 2019
d5de690
introduce currentAwsVersion const to track aws version
mjarmy Jul 3, 2019
4aea057
improve comments
mjarmy Jul 3, 2019
e868585
rearrange things once more
mjarmy Jul 4, 2019
d38f38b
conglomerate things into one function
mjarmy Jul 4, 2019
0e1150a
stub out aws auth initialize e2e test
mjarmy Jul 3, 2019
e8da5e5
improve aws auth initialize e2e test
mjarmy Jul 3, 2019
8e3e3f9
finish aws auth initialize e2e test
mjarmy Jul 4, 2019
b1603a3
tinker with aws auth initialize e2e test
mjarmy Jul 4, 2019
9a13a44
tinker with aws auth initialize e2e test
mjarmy Jul 4, 2019
7a2044e
tinker with aws auth initialize e2e test
mjarmy Jul 5, 2019
840aa89
fix typo in test suite
mjarmy Jul 5, 2019
9d2275c
simplify logic a tad
mjarmy Jul 5, 2019
fcdae78
rearrange assignment
mjarmy Jul 5, 2019
a8afc74
Fix a few lifecycle related issues in #7025 (#7075)
briankassouf Jul 5, 2019
d59209f
Merge branch 'master' into aws-upgrade-role-entries-v2
briankassouf Jul 5, 2019
f50bf2f
Fix panic when plugin fails to load
Jul 5, 2019
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
7 changes: 4 additions & 3 deletions builtin/credential/aws/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ type backend struct {
configMutex sync.RWMutex

// Lock to make changes to role entries
roleMutex sync.RWMutex
roleMutex sync.Mutex

// Lock to make changes to the blacklist entries
blacklistMutex sync.RWMutex
Expand Down Expand Up @@ -134,8 +134,9 @@ func Backend(conf *logical.BackendConfig) (*backend, error) {
pathIdentityWhitelist(b),
pathTidyIdentityWhitelist(b),
},
Invalidate: b.invalidate,
BackendType: logical.TypeCredential,
Invalidate: b.invalidate,
InitializeFunc: b.initialize,
BackendType: logical.TypeCredential,
}

return b, nil
Expand Down
47 changes: 47 additions & 0 deletions builtin/credential/aws/backend_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ import (

"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/sts"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/vault/api"
logicaltest "github.com/hashicorp/vault/helper/testhelpers/logical"
vaulthttp "github.com/hashicorp/vault/http"
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/helper/logging"
"github.com/hashicorp/vault/sdk/helper/policyutil"
"github.com/hashicorp/vault/sdk/logical"
"github.com/hashicorp/vault/vault"
)

const testVaultHeaderValue = "VaultAcceptanceTesting"
Expand Down Expand Up @@ -1813,3 +1818,45 @@ func generateRenewRequest(s logical.Storage, auth *logical.Auth) *logical.Reques

return renewReq
}

func TestBackend_E2E_Initialize(t *testing.T) {

// TODO: All this test does is load up the aws plugin, so that if we are in
// verbose mode we can observe in the log output that the initialization
// process ran:
//
// go test -v ./builtin/credential/aws/ -run TestBackend_E2E_Initialize
//
// We need to modify this test so that it:
// loads the plugin
// writes some roles
// "downgrades"the roles manually by directly modifying storage
// somehow re-triggers an Initialize(), which will upgrade the roles
// read from storage to confirm that the roles are upgraded
// read from storage to confirm that the 'config/version' entry is there

// set up a test cluster
logger := logging.NewVaultLogger(hclog.Trace)
coreConfig := &vault.CoreConfig{
Logger: logger,
CredentialBackends: map[string]logical.Factory{
"aws": Factory,
},
}
cluster := vault.NewTestCluster(t, coreConfig, &vault.TestClusterOptions{
NumCores: 1,
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()

vault.TestWaitActive(t, cluster.Cores[0].Core)
client := cluster.Cores[0].Client

// load the auth plugin
if err := client.Sys().EnableAuthWithOptions("aws", &api.EnableAuthOptions{
Type: "aws",
}); err != nil {
t.Fatal(err)
}
}
120 changes: 120 additions & 0 deletions builtin/credential/aws/path_role.go
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,126 @@ func (b *backend) setRole(ctx context.Context, s logical.Storage, roleName strin
return nil
}

// initialize is used to initialize the AWS roles
func (b *backend) initialize(ctx context.Context, req *logical.InitializationRequest) error {

// on standbys and DR secondaries we do not want to run any kind of upgrade logic
if b.System().ReplicationState().HasState(consts.ReplicationPerformanceStandby | consts.ReplicationDRSecondary) {
return nil
}

// Initialize only if we are either:
// (1) A local mount.
// (2) Are _NOT_ a replicated performance secondary
if b.System().LocalMount() || !b.System().ReplicationState().HasState(consts.ReplicationPerformanceSecondary) {

s := req.Storage

logger := b.Logger().Named("initialize")
logger.Info("starting initialization")

go func() {
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
// The vault will become unsealed while this goroutine is running,
// so we could see some role requests block until the lock is
// released. However we'd rather see those requests block (and
// potentially start timing out) than allow a non-upgraded role to
// be fetched.
b.roleMutex.Lock()
defer b.roleMutex.Unlock()

upgraded, err := b.upgrade(ctx, s)
if err != nil {
logger.Error("error running initialization", "error", err)
return
}
if upgraded {
logger.Info("an upgrade was performed during initialization")
}
}()

}

return nil
}

// awsVersion stores info about the the latest aws version that we have
// upgraded to.
type awsVersion struct {
RoleVersion int `json:"role_verison"`
}

// upgrade does an upgrade, if necessary
func (b *backend) upgrade(ctx context.Context, s logical.Storage) (bool, error) {

entry, err := s.Get(ctx, "config/version")
if err != nil {
return false, err
}
var version awsVersion
if entry == nil {
version.RoleVersion = 0
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
} else {
err = entry.DecodeJSON(&version)
if err != nil {
return false, err
}
}

switch version.RoleVersion {
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
case 0:
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
fallthrough
case 1:
fallthrough
case 2:
err = b.upgradeRoles(ctx, s)
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return false, err
}
case currentRoleStorageVersion:
return false, nil

default:
return false, fmt.Errorf("unrecognized role version: %d", version.RoleVersion)
}

// save the current version
rsv := awsVersion{RoleVersion: currentRoleStorageVersion}
entry, err = logical.StorageEntryJSON("config/version", &rsv)
if err != nil {
return false, err
}
err = s.Put(ctx, entry)
if err != nil {
return false, err
}

return true, nil
}

// upgradeRoles upgrades the various aws roles
func (b *backend) upgradeRoles(ctx context.Context, s logical.Storage) error {
mjarmy marked this conversation as resolved.
Show resolved Hide resolved

// Read all the role names.
roleNames, err := s.List(ctx, "role/")
if err != nil {
return err
}

// Upgrade the roles as necessary.
mjarmy marked this conversation as resolved.
Show resolved Hide resolved
for _, roleName := range roleNames {
// make sure the context hasn't been canceled
if ctx.Err() != nil {
return err
}
_, err := b.roleInternal(ctx, s, roleName)
if err != nil {
return err
}
}

return nil
}

// If needed, updates the role entry and returns a bool indicating if it was updated
// (and thus needs to be persisted)
func (b *backend) upgradeRole(ctx context.Context, s logical.Storage, roleEntry *awsRoleEntry) (bool, error) {
Expand Down
177 changes: 177 additions & 0 deletions builtin/credential/aws/path_role_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,183 @@ func TestRoleEntryUpgradeV(t *testing.T) {
}
}

func TestRoleInitialize(t *testing.T) {

config := logical.TestBackendConfig()
storage := &logical.InmemStorage{}
config.StorageView = storage
b, err := Backend(config)
if err != nil {
t.Fatal(err)
}

ctx := context.Background()
err = b.Setup(ctx, config)
if err != nil {
t.Fatal(err)
}

// create some role entries, some of which will need to be upgraded
type testData struct {
name string
entry *awsRoleEntry
}

before := []testData{
{
name: "role1",
entry: &awsRoleEntry{
BoundIamRoleARNs: []string{"arn:aws:iam::000000000001:role/my_role_prefix"},
BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000001:instance-profile/my_profile-prefix"},
Version: 1,
},
},
{
name: "role2",
entry: &awsRoleEntry{
BoundIamRoleARNs: []string{"arn:aws:iam::000000000002:role/my_role_prefix"},
BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000002:instance-profile/my_profile-prefix"},
Version: 2,
},
},
{
name: "role3",
entry: &awsRoleEntry{
BoundIamRoleARNs: []string{"arn:aws:iam::000000000003:role/my_role_prefix"},
BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000003:instance-profile/my_profile-prefix"},
Version: currentRoleStorageVersion,
},
},
}

// put the entries in storage
for _, role := range before {
err = b.setRole(ctx, storage, role.name, role.entry)
if err != nil {
t.Fatal(err)
}
}

// upgrade all the entries
upgraded, err := b.upgrade(ctx, storage)
if err != nil {
t.Fatal(err)
}
if !upgraded {
t.Fatalf("expected upgrade")
}

// read the entries from storage
after := make([]testData, 0)
names, err := storage.List(ctx, "role/")
if err != nil {
t.Fatal(err)
}
for _, name := range names {
entry, err := b.role(ctx, storage, name)
if err != nil {
t.Fatal(err)
}
after = append(after, testData{name: name, entry: entry})
}

// make sure each entry is at the current version
expected := []testData{
{
name: "role1",
entry: &awsRoleEntry{
BoundIamRoleARNs: []string{"arn:aws:iam::000000000001:role/my_role_prefix"},
BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000001:instance-profile/my_profile-prefix"},
Version: currentRoleStorageVersion,
},
},
{
name: "role2",
entry: &awsRoleEntry{
BoundIamRoleARNs: []string{"arn:aws:iam::000000000002:role/my_role_prefix"},
BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000002:instance-profile/my_profile-prefix"},
Version: currentRoleStorageVersion,
},
},
{
name: "role3",
entry: &awsRoleEntry{
BoundIamRoleARNs: []string{"arn:aws:iam::000000000003:role/my_role_prefix"},
BoundIamInstanceProfileARNs: []string{"arn:aws:iam::000000000003:instance-profile/my_profile-prefix"},
Version: currentRoleStorageVersion,
},
},
}
if diff := deep.Equal(expected, after); diff != nil {
t.Fatal(diff)
}

// run it again -- nothing will happen
upgraded, err = b.upgrade(ctx, storage)
if err != nil {
t.Fatal(err)
}
if upgraded {
t.Fatalf("expected no upgrade")
}

// make sure saved role version is correct
entry, err := storage.Get(ctx, "config/version")
if err != nil {
t.Fatal(err)
}
var version awsVersion
err = entry.DecodeJSON(&version)
if err != nil {
t.Fatal(err)
}
if version.RoleVersion != currentRoleStorageVersion {
t.Fatalf("expected version %d, got %d", currentRoleStorageVersion, version.RoleVersion)
}

// stomp on the saved version
version.RoleVersion = 0
e2, err := logical.StorageEntryJSON("config/version", version)
if err != nil {
t.Fatal(err)
}
err = storage.Put(ctx, e2)
if err != nil {
t.Fatal(err)
}

// run it again -- now an upgrade will happen
upgraded, err = b.upgrade(ctx, storage)
if err != nil {
t.Fatal(err)
}
if !upgraded {
t.Fatalf("expected upgrade")
}
}

func TestRoleStorageVersion(t *testing.T) {

before := awsVersion{
RoleVersion: 42,
}

entry, err := logical.StorageEntryJSON("config/version", &before)
if err != nil {
t.Fatal(err)
}

var after awsVersion
err = entry.DecodeJSON(&after)
if err != nil {
t.Fatal(err)
}

if diff := deep.Equal(before, after); diff != nil {
t.Fatal(diff)
}
}

func resolveArnToFakeUniqueId(ctx context.Context, s logical.Storage, arn string) (string, error) {
return "FakeUniqueId1", nil
}
Loading