Files
threshold-plugin-azure/plugin/plugin_test.go
T

504 lines
13 KiB
Go
Raw Normal View History

2021-09-07 22:06:22 -04:00
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/assert"
2021-09-07 22:06:22 -04:00
"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)
require.NotEmpty(lhResp.GetHosts())
2021-09-07 22:06:22 -04:00
pretty.Println(lhResp.GetHosts())
}
func TestValidateCatalog(t *testing.T) {
cases := []struct {
name string
catalogAttrs *structpb.Struct
wantError bool
}{
{
name: "success",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
},
{
name: "bad type",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: "bad type",
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "nil",
catalogAttrs: nil,
wantError: true,
},
{
name: "rotation enabled",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: false,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "no client id",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "no tenant id",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: false,
constClientId: "client_id",
constSubscriptionId: "sub_id",
}),
wantError: true,
},
{
name: "no subscription id",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: false,
constClientId: "client_id",
constTenantId: "tenant_id",
}),
wantError: true,
},
{
name: "unrecognized field",
catalogAttrs: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
"unrecognized": "unrecognized",
}),
wantError: true,
},
}
2021-09-07 22:06:22 -04:00
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := validateCatalog(&hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: tc.catalogAttrs,
},
})
if tc.wantError {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}
}
func TestValidateSecrets(t *testing.T) {
cases := []struct {
name string
catalogSecrets *structpb.Struct
wantError bool
}{
{
name: "success",
catalogSecrets: wrapMap(t, map[string]interface{}{
constSecretValue: "value",
constSecretId: "secret_id",
}),
},
{
name: "nil secret value",
catalogSecrets: nil,
wantError: true,
},
{
name: "no secret value",
catalogSecrets: wrapMap(t, map[string]interface{}{
constSecretId: "secret_id",
}),
wantError: true,
},
{
name: "secret with last rotated time",
catalogSecrets: wrapMap(t, map[string]interface{}{
constSecretValue: "value",
constCredsLastRotatedTime: "something",
}),
wantError: true,
},
}
2021-09-07 22:06:22 -04:00
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := validateSecrets(tc.catalogSecrets)
if tc.wantError {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}
}
2021-09-07 22:06:22 -04:00
func TestValidateSet(t *testing.T) {
cases := []struct {
name string
setAttrs *structpb.Struct
wantError bool
}{
{
name: "success",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: "filter value",
}),
},
{
name: "nil",
setAttrs: nil,
},
{
name: "bad type",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: true,
}),
wantError: true,
},
{
name: "empty",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: "",
}),
wantError: true,
},
{
name: "unrecognized field",
setAttrs: wrapMap(t, map[string]interface{}{
constFilter: "filter value",
"unrecognized": "unrecognized",
}),
wantError: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := validateSet(&hostsets.HostSet{
Attrs: &hostsets.HostSet_Attributes{
Attributes: tc.setAttrs,
},
})
if tc.wantError {
assert.Error(t, got)
} else {
assert.NoError(t, got)
}
})
}
}
func TestCreateCatalog(t *testing.T) {
ctx := context.Background()
p := &AzurePlugin{}
attrs := wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
2021-09-07 22:06:22 -04:00
})
secrets := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
2021-09-07 22:06:22 -04:00
})
res, err := p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
Secrets: secrets,
}})
require.NoError(t, err)
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithExtra := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
"extraField": "extra_value",
2021-09-07 22:06:22 -04:00
})
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
Secrets: secretsWithExtra,
}})
require.NoError(t, err)
// still only persist the fields we care about.
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithRotationTime := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
constCredsLastRotatedTime: "something",
2021-09-07 22:06:22 -04:00
})
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
Secrets: secretsWithRotationTime,
}})
assert.Error(t, err)
}
2021-09-07 22:06:22 -04:00
func TestUpdateCatalog(t *testing.T) {
ctx := context.Background()
p := &AzurePlugin{}
oldCatalog := &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "foo",
constTenantId: "foo",
constSubscriptionId: "foo",
}),
},
}
attrs := wrapMap(t, map[string]interface{}{
constDisableCredentialRotation: true,
constClientId: "client_id",
constTenantId: "tenant_id",
constSubscriptionId: "sub_id",
2021-09-07 22:06:22 -04:00
})
res, err := p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
},
})
require.NoError(t, err)
assert.Nil(t, res.GetPersisted())
secrets := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
2021-09-07 22:06:22 -04:00
})
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
Secrets: secrets,
},
})
require.NoError(t, err)
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithExtra := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
"extraField": "extra_value",
2021-09-07 22:06:22 -04:00
})
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
Secrets: secretsWithExtra,
},
})
require.NoError(t, err)
// still only persist the fields we care about.
assert.Equal(t, res.GetPersisted().GetSecrets().AsMap(), secrets.AsMap())
secretsWithRotationTime := wrapMap(t, map[string]interface{}{
constSecretValue: "secret_value",
constSecretId: "secret_id",
constCredsLastRotatedTime: "something",
})
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
CurrentCatalog: oldCatalog,
NewCatalog: &hostcatalogs.HostCatalog{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: attrs,
},
Secrets: secretsWithRotationTime,
},
})
assert.Error(t, err)
2021-09-07 22:06:22 -04:00
}
2021-11-10 13:59:21 -05:00
func waitForCreds(t *testing.T, authzInfo *AuthorizationInfo, shouldWork bool) auth.Authorizer {
2021-09-07 22:06:22 -04:00
t.Helper()
require := require.New(t)
ctx := context.Background()
auth, err := auth.NewClientSecretAuthorizer(
ctx,
environments.Global,
environments.Global.MsGraph,
2021-09-07 22:06:22 -04:00
auth.TokenVersion2,
2021-11-10 13:59:21 -05:00
authzInfo.AuthParams.TenantId,
nil,
2021-11-10 13:59:21 -05:00
authzInfo.AuthParams.ClientId,
authzInfo.AuthParams.SecretValue)
2021-09-07 22:06:22 -04:00
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{
Attrs: &hostcatalogs.HostCatalog_Attributes{
Attributes: wrapMap(t, map[string]interface{}{
constSubscriptionId: subscriptionId,
constTenantId: tenantId,
constClientId: clientId,
}),
},
2021-09-07 22:06:22 -04:00
Secrets: wrapMap(t, map[string]interface{}{
constSecretValue: secretValue,
}),
}
hses := []*hostsets.HostSet{
{
Id: "set1",
Attrs: &hostsets.HostSet_Attributes{
Attributes: wrapMap(t, map[string]interface{}{
constFilter: constDefaultFilter,
}),
},
2021-09-07 22:06:22 -04:00
},
{
Id: "set2",
Attrs: &hostsets.HostSet_Attributes{
Attributes: wrapMap(t, map[string]interface{}{
constFilter: constDefaultFilter,
}),
},
2021-09-07 22:06:22 -04:00
},
}
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.
2021-11-10 13:59:21 -05:00
func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog) (*AuthorizationInfo, *msgraph.PasswordCredential, func()) {
2021-09-07 22:06:22 -04:00
require := require.New(t)
ctx := context.Background()
var initialCred *msgraph.PasswordCredential
// Set up the data, get the config
2021-11-10 13:59:21 -05:00
authzInfo, err := getAuthorizationInfo(hostCatalog)
2021-09-07 22:06:22 -04:00
require.NoError(err)
// Get the credential
2021-11-10 13:59:21 -05:00
initialCred, err = addCredential(ctx, authzInfo)
2021-09-07 22:06:22 -04:00
require.NoError(err)
require.NotNil(initialCred)
require.NotNil(initialCred.KeyId)
require.NotNil(initialCred.SecretText)
2021-11-10 13:59:21 -05:00
origAuthConfig := *authzInfo
origAuthConfig.AuthParams.SecretId = *initialCred.KeyId
2021-09-07 22:06:22 -04:00
cleanup := func() {
if err := removeCredential(
ctx,
&origAuthConfig,
); err != nil {
// It may not exist because it's been rotated
require.Contains(err.Error(), "No password credential found with keyId", err.Error())
}
}
2021-11-10 13:59:21 -05:00
authzInfo.AuthParams.SecretId = *initialCred.KeyId
authzInfo.AuthParams.SecretValue = *initialCred.SecretText
authzInfo.HamiltonConfig.ClientSecret = *initialCred.SecretText
waitForCreds(t, authzInfo, true)
2021-09-07 22:06:22 -04:00
2021-11-10 13:59:21 -05:00
return authzInfo, initialCred, cleanup
2021-09-07 22:06:22 -04:00
}