2026-05-11 22:38:19 -07:00
|
|
|
// Copyright IBM Corp. 2021, 2026
|
2023-01-03 22:03:54 +00:00
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
2021-09-07 22:06:22 -04:00
|
|
|
package plugin
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
2025-01-22 18:36:03 +00:00
|
|
|
"reflect"
|
|
|
|
|
"strings"
|
2021-09-07 22:06:22 -04:00
|
|
|
"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"
|
2021-11-16 16:06:33 -07:00
|
|
|
"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)
|
2022-08-17 14:00:52 -04:00
|
|
|
require.NotEmpty(lhResp.GetHosts())
|
2021-09-07 22:06:22 -04:00
|
|
|
|
|
|
|
|
pretty.Println(lhResp.GetHosts())
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 16:06:33 -07:00
|
|
|
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",
|
|
|
|
|
}),
|
|
|
|
|
},
|
2021-11-23 08:57:08 -07:00
|
|
|
{
|
|
|
|
|
name: "bad type",
|
|
|
|
|
catalogAttrs: wrapMap(t, map[string]interface{}{
|
|
|
|
|
constDisableCredentialRotation: "bad type",
|
|
|
|
|
constClientId: "client_id",
|
|
|
|
|
constTenantId: "tenant_id",
|
|
|
|
|
constSubscriptionId: "sub_id",
|
|
|
|
|
}),
|
|
|
|
|
wantError: true,
|
|
|
|
|
},
|
2021-11-16 16:06:33 -07:00
|
|
|
{
|
|
|
|
|
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
|
|
|
|
2021-11-16 16:06:33 -07:00
|
|
|
for _, tc := range cases {
|
|
|
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
|
|
|
got := validateCatalog(&hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: tc.catalogAttrs,
|
|
|
|
|
},
|
2021-11-16 16:06:33 -07:00
|
|
|
})
|
|
|
|
|
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
|
|
|
|
2021-11-16 16:06:33 -07: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
|
|
|
|
2021-11-23 08:57:08 -07: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{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: tc.setAttrs,
|
|
|
|
|
},
|
2021-11-23 08:57:08 -07:00
|
|
|
})
|
|
|
|
|
if tc.wantError {
|
|
|
|
|
assert.Error(t, got)
|
|
|
|
|
} else {
|
|
|
|
|
assert.NoError(t, got)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-16 16:06:33 -07:00
|
|
|
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{}{
|
2021-11-16 16:06:33 -07:00
|
|
|
constSecretValue: "secret_value",
|
|
|
|
|
constSecretId: "secret_id",
|
2021-09-07 22:06:22 -04:00
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
|
|
|
|
|
res, err := p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
Secrets: secrets,
|
2021-11-16 16:06:33 -07:00
|
|
|
}})
|
|
|
|
|
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
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
Secrets: secretsWithExtra,
|
2021-11-16 16:06:33 -07:00
|
|
|
}})
|
|
|
|
|
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
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
res, err = p.OnCreateCatalog(ctx, &pb.OnCreateCatalogRequest{Catalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
Secrets: secretsWithRotationTime,
|
2021-11-16 16:06:33 -07:00
|
|
|
}})
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
}
|
2021-09-07 22:06:22 -04:00
|
|
|
|
2021-11-16 16:06:33 -07:00
|
|
|
func TestUpdateCatalog(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
p := &AzurePlugin{}
|
|
|
|
|
|
|
|
|
|
oldCatalog := &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
|
|
|
|
constDisableCredentialRotation: true,
|
|
|
|
|
constClientId: "foo",
|
|
|
|
|
constTenantId: "foo",
|
|
|
|
|
constSubscriptionId: "foo",
|
|
|
|
|
}),
|
|
|
|
|
},
|
2021-11-16 16:06:33 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
})
|
|
|
|
|
|
2022-08-17 14:00:52 -04:00
|
|
|
res, err := p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: oldCatalog,
|
2021-11-16 16:06:33 -07:00
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
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
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
|
2022-08-17 14:00:52 -04:00
|
|
|
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: oldCatalog,
|
2021-11-16 16:06:33 -07:00
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
Secrets: secrets,
|
|
|
|
|
},
|
|
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
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
|
|
|
})
|
2022-08-17 14:00:52 -04:00
|
|
|
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: oldCatalog,
|
2021-11-16 16:06:33 -07:00
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
Secrets: secretsWithExtra,
|
|
|
|
|
},
|
|
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
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",
|
|
|
|
|
})
|
2022-08-17 14:00:52 -04:00
|
|
|
res, err = p.OnUpdateCatalog(ctx, &pb.OnUpdateCatalogRequest{
|
|
|
|
|
CurrentCatalog: oldCatalog,
|
2021-11-16 16:06:33 -07:00
|
|
|
NewCatalog: &hostcatalogs.HostCatalog{
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostcatalogs.HostCatalog_Attributes{
|
|
|
|
|
Attributes: attrs,
|
|
|
|
|
},
|
|
|
|
|
Secrets: secretsWithRotationTime,
|
|
|
|
|
},
|
|
|
|
|
})
|
2021-11-16 16:06:33 -07:00
|
|
|
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,
|
2022-08-17 14:00:52 -04:00
|
|
|
environments.Global.MsGraph,
|
2021-09-07 22:06:22 -04:00
|
|
|
auth.TokenVersion2,
|
2021-11-10 13:59:21 -05:00
|
|
|
authzInfo.AuthParams.TenantId,
|
2022-08-17 14:00:52 -04:00
|
|
|
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{
|
2022-08-17 14:00:52 -04:00
|
|
|
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",
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
2025-01-22 18:36:03 +00:00
|
|
|
constFilter: "",
|
2022-08-17 14:00:52 -04:00
|
|
|
}),
|
|
|
|
|
},
|
2021-09-07 22:06:22 -04:00
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
Id: "set2",
|
2022-08-17 14:00:52 -04:00
|
|
|
Attrs: &hostsets.HostSet_Attributes{
|
|
|
|
|
Attributes: wrapMap(t, map[string]interface{}{
|
2025-01-22 18:36:03 +00:00
|
|
|
constFilter: "",
|
2022-08-17 14:00:52 -04:00
|
|
|
}),
|
|
|
|
|
},
|
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
|
|
|
}
|
2025-01-22 18:36:03 +00:00
|
|
|
|
|
|
|
|
func TestSplitId(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
input string
|
|
|
|
|
expectedService string
|
|
|
|
|
expectedResource string
|
|
|
|
|
wantRG string
|
|
|
|
|
wantName string
|
|
|
|
|
wantErr bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "valid resource id",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "rg1",
|
|
|
|
|
wantName: "vm1",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid with different case",
|
|
|
|
|
input: "/SUBSCRIPTIONS/sub1/RESOURCEGROUPS/rg1/PROVIDERS/MICROSOFT.COMPUTE/VIRTUALMACHINES/vm1",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "rg1",
|
|
|
|
|
wantName: "vm1",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid - trailing slash",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1/",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "",
|
|
|
|
|
wantName: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid - wrong service",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Storage/virtualMachines/vm1",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "",
|
|
|
|
|
wantName: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid - wrong resource type",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "",
|
|
|
|
|
wantName: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid - missing segments",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "",
|
|
|
|
|
wantName: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid - empty string",
|
|
|
|
|
input: "",
|
|
|
|
|
expectedService: "Microsoft.Compute",
|
|
|
|
|
expectedResource: "virtualMachines",
|
|
|
|
|
wantRG: "",
|
|
|
|
|
wantName: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
gotRG, gotName, err := splitId(tt.input, tt.expectedService, tt.expectedResource)
|
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
|
t.Errorf("splitId() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if gotRG != tt.wantRG {
|
|
|
|
|
t.Errorf("splitId() gotRG = %v, want %v", gotRG, tt.wantRG)
|
|
|
|
|
}
|
|
|
|
|
if gotName != tt.wantName {
|
|
|
|
|
t.Errorf("splitId() gotName = %v, want %v", gotName, tt.wantName)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestExtractResourceType(t *testing.T) {
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
input string
|
|
|
|
|
want string
|
|
|
|
|
wantErr bool
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "valid compute resource",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1",
|
|
|
|
|
want: "virtualMachines",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid compute resource different case",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/MICROSOFT.COMPUTE/virtualMachines/vm1",
|
|
|
|
|
want: "virtualMachines",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid with trailing slash",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm1/",
|
|
|
|
|
want: "virtualMachines",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "different compute resource type",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/diskEncryptionSets/des1",
|
|
|
|
|
want: "diskEncryptionSets",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "non-compute resource",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Storage/storageAccounts/sa1",
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "malformed - missing provider",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/Microsoft.Compute/virtualMachines/vm1",
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "malformed - missing resource type",
|
|
|
|
|
input: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute",
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "empty string",
|
|
|
|
|
input: "",
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "too short",
|
|
|
|
|
input: "/subscriptions/sub1",
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "malformed structure",
|
|
|
|
|
input: "/something/else/entirely",
|
|
|
|
|
want: "",
|
|
|
|
|
wantErr: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got, err := extractResourceType(tt.input)
|
|
|
|
|
if (err != nil) != tt.wantErr {
|
|
|
|
|
t.Errorf("extractResourceType() error = %v, wantErr %v", err, tt.wantErr)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if got != tt.want {
|
|
|
|
|
t.Errorf("extractResourceType() = %v, want %v", got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestCreateHostFromResource(t *testing.T) {
|
|
|
|
|
// Setup common test data
|
|
|
|
|
testNetwork := networkInfo{
|
|
|
|
|
IpAddresses: []string{"192.168.1.1", "192.168.1.2"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use full Azure resource paths as keys
|
|
|
|
|
vmssPath := "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachineScaleSets/vmss1"
|
|
|
|
|
vmPath := "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachines/vm-resource1"
|
|
|
|
|
|
|
|
|
|
resourceToSetMap := map[string][]string{
|
|
|
|
|
vmssPath: {"set1", "set2"},
|
|
|
|
|
vmPath: {"set3", "set4"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
resourceID string
|
|
|
|
|
network networkInfo
|
|
|
|
|
setMap map[string][]string
|
|
|
|
|
want *pb.ListHostsResponseHost
|
|
|
|
|
wantErr bool
|
|
|
|
|
errMessage string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "valid VMSS resource",
|
|
|
|
|
resourceID: vmssPath + "/virtualMachines/vm1",
|
|
|
|
|
network: testNetwork,
|
|
|
|
|
setMap: resourceToSetMap,
|
|
|
|
|
want: &pb.ListHostsResponseHost{
|
|
|
|
|
ExternalId: vmssPath + "/virtualMachines/vm1",
|
|
|
|
|
ExternalName: "vmss1_vm1",
|
|
|
|
|
IpAddresses: []string{"192.168.1.1", "192.168.1.2"},
|
|
|
|
|
SetIds: []string{"set1", "set2"},
|
|
|
|
|
},
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "valid VM resource",
|
|
|
|
|
resourceID: vmPath,
|
|
|
|
|
network: testNetwork,
|
|
|
|
|
setMap: resourceToSetMap,
|
|
|
|
|
want: &pb.ListHostsResponseHost{
|
|
|
|
|
ExternalId: vmPath,
|
|
|
|
|
ExternalName: "vm-resource1",
|
|
|
|
|
IpAddresses: []string{"192.168.1.1", "192.168.1.2"},
|
|
|
|
|
SetIds: []string{"set3", "set4"},
|
|
|
|
|
},
|
|
|
|
|
wantErr: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "invalid resource ID",
|
|
|
|
|
resourceID: "invalid-resource-id",
|
|
|
|
|
network: testNetwork,
|
|
|
|
|
setMap: resourceToSetMap,
|
|
|
|
|
want: nil,
|
|
|
|
|
wantErr: true,
|
|
|
|
|
errMessage: "invalid resource ID",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "unsupported resource type",
|
|
|
|
|
resourceID: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/unsupportedResource/resource1",
|
|
|
|
|
network: testNetwork,
|
|
|
|
|
setMap: resourceToSetMap,
|
|
|
|
|
want: nil,
|
|
|
|
|
wantErr: true,
|
|
|
|
|
errMessage: "unsupported resource type",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "VMSS with valid resource type but invalid structure",
|
|
|
|
|
resourceID: "/subscriptions/sub1/resourceGroups/rg1/providers/Microsoft.Compute/virtualMachineScaleSets", // Missing required segments after virtualMachineScaleSets
|
|
|
|
|
network: testNetwork,
|
|
|
|
|
setMap: resourceToSetMap,
|
|
|
|
|
want: nil,
|
|
|
|
|
wantErr: true,
|
|
|
|
|
errMessage: "could not determine set for resource ID: not a valid VMSS resource ID",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
got, err := createHostFromResource(tt.resourceID, tt.network, tt.setMap)
|
|
|
|
|
|
|
|
|
|
// Check error cases
|
|
|
|
|
if tt.wantErr {
|
|
|
|
|
if err == nil {
|
|
|
|
|
t.Errorf("createHostFromResource() expected error containing %q, got nil", tt.errMessage)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if !strings.Contains(err.Error(), tt.errMessage) {
|
|
|
|
|
t.Errorf("createHostFromResource() error = %v, want error containing %q", err, tt.errMessage)
|
|
|
|
|
}
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Check non-error cases
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Errorf("createHostFromResource() unexpected error: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Compare the results
|
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
|
|
|
t.Errorf("createHostFromResource() = %v, want %v", got, tt.want)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|