Initial commit
This commit is contained in:
commit
bb97249db8
11 files changed
+2604
No files matched your search
@@ -0,0 +1,274 @@
|
||||
package plugin
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostsets"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
"github.com/hashicorp/go-secure-stdlib/parseutil"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/manicminer/hamilton/auth"
|
||||
"github.com/manicminer/hamilton/environments"
|
||||
"github.com/manicminer/hamilton/msgraph"
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
)
|
||||
|
||||
func wrapMap(t *testing.T, in map[string]interface{}) *structpb.Struct {
|
||||
t.Helper()
|
||||
out, err := structpb.NewStruct(in)
|
||||
require.NoError(t, err)
|
||||
return out
|
||||
}
|
||||
|
||||
// TestListHosts tests the ListHosts function
|
||||
func TestListHosts(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
p := &AzurePlugin{}
|
||||
hostCatalog, hostSets := testGetHostStructs(t)
|
||||
secrets := hostCatalog.Secrets
|
||||
lhResp, err := p.ListHosts(ctx, &pb.ListHostsRequest{
|
||||
Catalog: hostCatalog,
|
||||
Sets: hostSets,
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: secrets,
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
require.NotNil(lhResp)
|
||||
|
||||
pretty.Println(lhResp.GetHosts())
|
||||
}
|
||||
|
||||
func TestOnCreateOnDeleteCatalog(t *testing.T) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
p := &AzurePlugin{}
|
||||
hostCatalog, _ := testGetHostStructs(t)
|
||||
objId, initialAuthConfig, initialCred, cleanup := testCreateInitialSecret(t, hostCatalog)
|
||||
defer cleanup()
|
||||
|
||||
// At this point, remove secrets corresponding to those read in by
|
||||
// testGetHostCatalog as they're our permanent ones
|
||||
hostCatalog.Secrets = nil
|
||||
|
||||
// Give some time -- a lot -- for token to become active
|
||||
time.Sleep(10 * time.Minute)
|
||||
|
||||
// Now, we'll use this credential to simulate creating a host catalog, which
|
||||
// will rotate it. First, ensure we don't error out if creds aren't provided
|
||||
// _yet_ (instead, on update)
|
||||
ccResp, err := p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{
|
||||
Catalog: hostCatalog,
|
||||
})
|
||||
require.NoError(err)
|
||||
require.Nil(ccResp)
|
||||
|
||||
// Now run the same thing with rotation disabled, make sure we get the same
|
||||
// values back
|
||||
secrets := wrapMap(t, map[string]interface{}{
|
||||
constSecretValue: *initialCred.SecretText,
|
||||
constSecretId: *initialCred.KeyId,
|
||||
})
|
||||
hostCatalog.Secrets = secrets
|
||||
hostCatalog.GetAttributes().GetFields()[constDisableCredentialRotation] = structpb.NewBoolValue(true)
|
||||
ccResp, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{
|
||||
Catalog: hostCatalog,
|
||||
})
|
||||
require.NoError(err)
|
||||
require.NotNil(ccResp)
|
||||
require.NotNil(ccResp.GetPersisted())
|
||||
require.NotNil(ccResp.GetPersisted().GetSecrets())
|
||||
newPdFields := ccResp.GetPersisted().GetSecrets().GetFields()
|
||||
require.Equal(*initialCred.KeyId, newPdFields[constSecretId].GetStringValue())
|
||||
require.Equal(*initialCred.SecretText, newPdFields[constSecretValue].GetStringValue())
|
||||
|
||||
// Now we'll try again, actually rotating the creds
|
||||
delete(hostCatalog.GetAttributes().GetFields(), constDisableCredentialRotation)
|
||||
ccResp, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{
|
||||
Catalog: hostCatalog,
|
||||
})
|
||||
|
||||
require.NoError(err)
|
||||
require.NotNil(ccResp)
|
||||
require.NotNil(ccResp.GetPersisted())
|
||||
require.NotNil(ccResp.GetPersisted().GetSecrets())
|
||||
newPdFields = ccResp.GetPersisted().GetSecrets().GetFields()
|
||||
// Use old creds for this removal to give it a high likelihood of success
|
||||
deferAuthConfig := *initialAuthConfig
|
||||
defer func() {
|
||||
_ = removeCredential(
|
||||
ctx,
|
||||
&deferAuthConfig,
|
||||
WithObjectId(objId),
|
||||
)
|
||||
}()
|
||||
|
||||
// Best-effort ensure that this new cred is deleted if OnDelete doesn't work right
|
||||
require.NotEqual(*initialCred.KeyId, newPdFields[constSecretId].GetStringValue())
|
||||
require.NotEqual(*initialCred.SecretText, newPdFields[constSecretValue].GetStringValue())
|
||||
|
||||
// Remove these now prior to OnDelete
|
||||
hostCatalog.Secrets = nil
|
||||
|
||||
// Make sure old creds don't work
|
||||
waitForCredsAuthConfig := *initialAuthConfig
|
||||
// FIXME: Actually don't, because they can still be used for up to a day!??! later?!?!
|
||||
// waitForCreds(t, &waitForCredsAuthConfig, false)
|
||||
|
||||
// Make sure the new creds work, then sleep
|
||||
waitForCredsAuthConfig.SecretId = newPdFields[constSecretId].GetStringValue()
|
||||
waitForCredsAuthConfig.ClientSecret = newPdFields[constSecretValue].GetStringValue()
|
||||
waitForCreds(t, &waitForCredsAuthConfig, true)
|
||||
time.Sleep(10 * time.Minute)
|
||||
|
||||
// Now, let's test OnDelete. First, check the case where we never configured
|
||||
// a credential in the first place.
|
||||
cdResp, err := p.OnDeleteCatalog(ctx, &pb.OnDeleteCatalogRequest{
|
||||
Catalog: hostCatalog,
|
||||
Persisted: &pb.HostCatalogPersisted{},
|
||||
})
|
||||
require.NoError(err)
|
||||
require.Nil(cdResp)
|
||||
|
||||
// Now, use the credentials we got above to revoke this credential we got from OnCreate
|
||||
secrets = wrapMap(t, map[string]interface{}{
|
||||
constSecretValue: waitForCredsAuthConfig.ClientSecret,
|
||||
constSecretId: waitForCredsAuthConfig.SecretId,
|
||||
})
|
||||
cdResp, err = p.OnDeleteCatalog(ctx, &pb.OnDeleteCatalogRequest{
|
||||
Catalog: hostCatalog,
|
||||
Persisted: &pb.HostCatalogPersisted{
|
||||
Secrets: secrets,
|
||||
},
|
||||
})
|
||||
require.NoError(err)
|
||||
require.Nil(cdResp)
|
||||
// Make sure the creds no longer work
|
||||
// FIXME: Don't, for the same reason
|
||||
// waitForCreds(t, &waitForCredsAuthConfig, false)
|
||||
}
|
||||
|
||||
func waitForCreds(t *testing.T, hConfig *WrappedHamiltonConfig, shouldWork bool) auth.Authorizer {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
auth, err := auth.NewClientSecretAuthorizer(
|
||||
ctx,
|
||||
environments.Global,
|
||||
auth.MsGraph,
|
||||
auth.TokenVersion2,
|
||||
hConfig.TenantID,
|
||||
hConfig.ClientID,
|
||||
hConfig.ClientSecret)
|
||||
require.NoError(err)
|
||||
|
||||
for {
|
||||
_, err := auth.Token()
|
||||
switch {
|
||||
case shouldWork && err == nil:
|
||||
return auth
|
||||
case !shouldWork && err != nil:
|
||||
return nil
|
||||
default:
|
||||
time.Sleep(30 * time.Second)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testGetHostStructs(t *testing.T) (*hostcatalogs.HostCatalog, []*hostsets.HostSet) {
|
||||
require := require.New(t)
|
||||
wd, err := os.Getwd()
|
||||
require.NoError(err)
|
||||
require.NotEmpty(wd)
|
||||
clientId, err := parseutil.ParsePath("file://" + filepath.Join(wd, "private", "clientid"))
|
||||
require.NoError(err)
|
||||
secretValue, err := parseutil.ParsePath("file://" + filepath.Join(wd, "private", "secretvalue"))
|
||||
require.NoError(err)
|
||||
tenantId, err := parseutil.ParsePath("file://" + filepath.Join(wd, "private", "tenantid"))
|
||||
require.NoError(err)
|
||||
subscriptionId, err := parseutil.ParsePath("file://" + filepath.Join(wd, "private", "subscriptionid"))
|
||||
require.NoError(err)
|
||||
|
||||
// These values will not change througout the test
|
||||
hc := &hostcatalogs.HostCatalog{
|
||||
Attributes: wrapMap(t, map[string]interface{}{
|
||||
constSubscriptionId: subscriptionId,
|
||||
constTenantId: tenantId,
|
||||
constClientId: clientId,
|
||||
}),
|
||||
Secrets: wrapMap(t, map[string]interface{}{
|
||||
constSecretValue: secretValue,
|
||||
}),
|
||||
}
|
||||
|
||||
hses := []*hostsets.HostSet{
|
||||
{
|
||||
Id: "set1",
|
||||
Attributes: wrapMap(t, map[string]interface{}{
|
||||
constFilter: constDefaultFilter,
|
||||
}),
|
||||
},
|
||||
{
|
||||
Id: "set2",
|
||||
Attributes: wrapMap(t, map[string]interface{}{
|
||||
constFilter: constDefaultFilter,
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
return hc, hses
|
||||
}
|
||||
|
||||
// This function creates a new secret so we don't revoke the original one
|
||||
// leading into a test. This will serve as our "initial secret" for a test. We
|
||||
// also return a few cached items. It is expected that the host catalog contains
|
||||
// Secrets.
|
||||
func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog) (string, *WrappedHamiltonConfig, *msgraph.PasswordCredential, func()) {
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
|
||||
var initialCred *msgraph.PasswordCredential
|
||||
var objId string // The object ID of the application
|
||||
|
||||
// Set up the data, get the config
|
||||
persistedData := hostCatalog.GetSecrets()
|
||||
authConfig, err := getWrappedHamiltonConfig(hostCatalog, &pb.HostCatalogPersisted{Secrets: persistedData})
|
||||
require.NoError(err)
|
||||
|
||||
// Pre-cache the object ID
|
||||
objId, err = getObjectId(ctx, authConfig, WithClientId(hostCatalog.GetAttributes().GetFields()[constClientId].GetStringValue()))
|
||||
require.NoError(err)
|
||||
|
||||
// Get the credential
|
||||
initialCred, err = addCredential(ctx, authConfig, WithObjectId(objId))
|
||||
require.NoError(err)
|
||||
require.NotNil(initialCred)
|
||||
require.NotNil(initialCred.KeyId)
|
||||
require.NotNil(initialCred.SecretText)
|
||||
|
||||
origAuthConfig := *authConfig
|
||||
cleanup := func() {
|
||||
if err := removeCredential(
|
||||
ctx,
|
||||
&origAuthConfig,
|
||||
WithObjectId(objId),
|
||||
WithSecretId(*initialCred.KeyId),
|
||||
); err != nil {
|
||||
// It may not exist because it's been rotated
|
||||
require.Contains(err.Error(), "No password credential found with keyId", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
authConfig.SecretId = *initialCred.KeyId
|
||||
authConfig.ClientSecret = *initialCred.SecretText
|
||||
waitForCreds(t, authConfig, true)
|
||||
|
||||
return objId, authConfig, initialCred, cleanup
|
||||
}
|
||||
Reference in new issue
Block a user