Massive cleanup. Tests are passing.
This commit is contained in:
1 parent
073c3e1904
commit
933fc23905
6 files changed
+208
-248
No files matched your search
@@ -12,6 +12,7 @@ require (
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.1
|
||||
github.com/kr/pretty v0.3.0
|
||||
github.com/manicminer/hamilton v0.32.0
|
||||
github.com/mitchellh/mapstructure v1.4.1
|
||||
github.com/stretchr/testify v1.7.0
|
||||
google.golang.org/protobuf v1.27.1
|
||||
)
|
||||
+140
-165
@@ -9,196 +9,167 @@ import (
|
||||
"github.com/Azure/go-autorest/autorest"
|
||||
"github.com/Azure/go-autorest/autorest/azure/auth"
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||
pb "github.com/hashicorp/boundary/sdk/pbs/plugin"
|
||||
hauth "github.com/manicminer/hamilton/auth"
|
||||
"github.com/manicminer/hamilton/environments"
|
||||
"github.com/manicminer/hamilton/msgraph"
|
||||
"github.com/manicminer/hamilton/odata"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
)
|
||||
|
||||
type WrappedHamiltonConfig struct {
|
||||
hauth.Config
|
||||
SecretId string
|
||||
type AuthorizationInfo struct {
|
||||
HamiltonConfig hauth.Config
|
||||
AuthParams AuthParams
|
||||
}
|
||||
|
||||
func authParams(hc *hostcatalogs.HostCatalog, secrets *structpb.Struct) (map[string]string, error) {
|
||||
type Attributes struct {
|
||||
SubscriptionId string `mapstructure:"subscription_id"`
|
||||
ClientId string `mapstructure:"client_id"`
|
||||
TenantId string `mapstructure:"tenant_id"`
|
||||
DisableCredentialRotation bool `mapstructure:"disable_credential_rotation"`
|
||||
}
|
||||
|
||||
type SecretData struct {
|
||||
SecretValue string `mapstructure:"secret_value"`
|
||||
SecretId string `mapstructure:"secret_id"`
|
||||
CredsLastRotatedTime string `mapstructure:"creds_last_rotated_time"`
|
||||
}
|
||||
|
||||
type AuthParams struct {
|
||||
SubscriptionId string
|
||||
ClientId string
|
||||
ClientObjectId string
|
||||
TenantId string
|
||||
SecretValue string
|
||||
SecretId string
|
||||
CredsLastRotatedTime time.Time
|
||||
}
|
||||
|
||||
func getAuthorizationInfo(hc *hostcatalogs.HostCatalog) (*AuthorizationInfo, error) {
|
||||
if hc == nil {
|
||||
return nil, errors.New("host catalog is nil")
|
||||
}
|
||||
if hc.GetAttributes() == nil {
|
||||
return nil, errors.New("host catalog attributes is nil")
|
||||
}
|
||||
hcFields := hc.GetAttributes().GetFields()
|
||||
if hcFields == nil {
|
||||
return nil, errors.New("host catalog attributes fields is nil")
|
||||
if hc.GetSecrets() == nil {
|
||||
return nil, errors.New("host catalog secret data is nil")
|
||||
}
|
||||
|
||||
if secrets == nil {
|
||||
return nil, errors.New("secrets is nil")
|
||||
hcFields := hc.GetAttributes().AsMap()
|
||||
var attrs Attributes
|
||||
if err := mapstructure.Decode(hcFields, &attrs); err != nil {
|
||||
return nil, fmt.Errorf("error decoding host catalog attribute fields: %w", err)
|
||||
}
|
||||
pdFields := secrets.GetFields()
|
||||
if pdFields == nil {
|
||||
return nil, errors.New("secrets data fields is nil")
|
||||
if attrs.SubscriptionId == "" {
|
||||
return nil, errors.New("subscription id is empty")
|
||||
}
|
||||
if attrs.TenantId == "" {
|
||||
return nil, errors.New("tenant id is empty")
|
||||
}
|
||||
|
||||
var subscriptionId string
|
||||
if hcFields[constSubscriptionId] == nil {
|
||||
return nil, errors.New("subscription id not provided in incoming data")
|
||||
pdFields := hc.GetSecrets().AsMap()
|
||||
var secrets SecretData
|
||||
if err := mapstructure.Decode(pdFields, &secrets); err != nil {
|
||||
return nil, fmt.Errorf("error decoding host catalog persisted data fields: %w", err)
|
||||
}
|
||||
if subscriptionId = hcFields[constSubscriptionId].GetStringValue(); subscriptionId == "" {
|
||||
return nil, errors.New("subscription id could not be read as a string value")
|
||||
if secrets.SecretValue == "" {
|
||||
return nil, errors.New("secret value is empty")
|
||||
}
|
||||
|
||||
ret := map[string]string{
|
||||
constSubscriptionId: subscriptionId,
|
||||
}
|
||||
|
||||
clientId := hcFields[constClientId]
|
||||
secretValue := pdFields[constSecretValue]
|
||||
secretId := pdFields[constSecretId]
|
||||
tenantId := hcFields[constTenantId]
|
||||
|
||||
switch {
|
||||
case clientId != nil && clientId.GetStringValue() != "" &&
|
||||
secretValue != nil && secretValue.GetStringValue() != "" &&
|
||||
tenantId != nil && tenantId.GetStringValue() != "":
|
||||
ret[constClientId] = clientId.GetStringValue()
|
||||
ret[constSecretValue] = secretValue.GetStringValue()
|
||||
ret[constTenantId] = tenantId.GetStringValue()
|
||||
if secretId != nil {
|
||||
ret[constSecretId] = secretId.GetStringValue()
|
||||
}
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func fetchAuthorizer(hc *hostcatalogs.HostCatalog, secrets *structpb.Struct) (string, autorest.Authorizer, error) {
|
||||
aParams, err := authParams(hc, secrets)
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error finding authentication params: %w", err)
|
||||
}
|
||||
|
||||
var authorizer autorest.Authorizer
|
||||
// Switch between currently-understood authorization types
|
||||
{
|
||||
switch {
|
||||
case aParams[constClientId] != "" &&
|
||||
aParams[constSecretValue] != "" &&
|
||||
aParams[constTenantId] != "":
|
||||
credsConfig := auth.NewClientCredentialsConfig(
|
||||
aParams[constClientId],
|
||||
aParams[constSecretValue],
|
||||
aParams[constTenantId],
|
||||
)
|
||||
authorizer, err = credsConfig.Authorizer()
|
||||
if err != nil {
|
||||
return "", nil, fmt.Errorf("error authorizing to Azure: %w", err)
|
||||
}
|
||||
|
||||
default:
|
||||
return "", nil, errors.New("no or incomplete authentication information available")
|
||||
}
|
||||
|
||||
if authorizer == nil {
|
||||
return "", nil, fmt.Errorf("authorizer after authorizing to Azure is nil")
|
||||
}
|
||||
}
|
||||
|
||||
return aParams[constSubscriptionId], authorizer, nil
|
||||
}
|
||||
|
||||
func getWrappedHamiltonConfig(hc *hostcatalogs.HostCatalog, persisted *pb.HostCatalogPersisted) (*WrappedHamiltonConfig, error) {
|
||||
secrets := hc.GetSecrets()
|
||||
if secrets == nil && persisted != nil {
|
||||
secrets = persisted.GetSecrets()
|
||||
}
|
||||
aParams, err := authParams(hc, secrets)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching authentication params when fetching: %w", err)
|
||||
}
|
||||
authConfig := &WrappedHamiltonConfig{}
|
||||
authConfig.Environment = environments.Global
|
||||
|
||||
switch {
|
||||
case aParams[constClientId] != "" &&
|
||||
aParams[constSecretValue] != "" &&
|
||||
aParams[constTenantId] != "":
|
||||
authConfig.TenantID = aParams[constTenantId]
|
||||
authConfig.ClientID = aParams[constClientId]
|
||||
authConfig.ClientSecret = aParams[constSecretValue]
|
||||
if aParams[constSecretId] != "" {
|
||||
authConfig.SecretId = aParams[constSecretId]
|
||||
}
|
||||
authConfig.EnableClientSecretAuth = true
|
||||
|
||||
default:
|
||||
return nil, errors.New("no or incomplete authentication information available")
|
||||
}
|
||||
|
||||
return authConfig, nil
|
||||
}
|
||||
|
||||
func getObjectId(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (string, error) {
|
||||
opts, err := getOpts(opt...)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error parsing ops: %w", err)
|
||||
}
|
||||
|
||||
objId := opts.withObjectId
|
||||
if objId == "" {
|
||||
clientId := opts.withClientId
|
||||
if clientId == "" && hConfig != nil {
|
||||
clientId = hConfig.ClientID
|
||||
}
|
||||
if clientId == "" {
|
||||
return "", fmt.Errorf("neither client id nor object id passed in, cannot continue")
|
||||
}
|
||||
// First, find the appId
|
||||
aClient, err := getApplicationsClient(ctx, hConfig)
|
||||
var lastRotatedTime time.Time
|
||||
var err error
|
||||
if secrets.CredsLastRotatedTime != "" {
|
||||
lastRotatedTime, err = time.Parse(time.RFC3339Nano, secrets.CredsLastRotatedTime)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error getting application client: %w", err)
|
||||
return nil, fmt.Errorf("error parsing last rotated time: %w", err)
|
||||
}
|
||||
if aClient == nil {
|
||||
return "", errors.New("applications client is nil when getting object id")
|
||||
}
|
||||
apps, _, err := aClient.List(ctx, odata.Query{
|
||||
Filter: fmt.Sprintf("appId eq '%s'", clientId),
|
||||
})
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("error listing applications to find objId: %w", err)
|
||||
}
|
||||
if apps == nil {
|
||||
return "", errors.New("nil apps returned when listing to find objId")
|
||||
}
|
||||
if len(*apps) != 1 {
|
||||
return "", fmt.Errorf("unexpected number of apps found, expected 1, found %d", len(*apps))
|
||||
}
|
||||
objId = *(*apps)[0].DirectoryObject.ID
|
||||
}
|
||||
return objId, nil
|
||||
|
||||
return &AuthorizationInfo{
|
||||
HamiltonConfig: hauth.Config{
|
||||
Environment: environments.Global,
|
||||
TenantID: attrs.TenantId,
|
||||
ClientID: attrs.ClientId,
|
||||
ClientSecret: secrets.SecretValue,
|
||||
EnableClientSecretAuth: true,
|
||||
},
|
||||
AuthParams: AuthParams{
|
||||
SubscriptionId: attrs.SubscriptionId,
|
||||
ClientId: attrs.ClientId,
|
||||
TenantId: attrs.TenantId,
|
||||
SecretId: secrets.SecretId,
|
||||
SecretValue: secrets.SecretValue,
|
||||
CredsLastRotatedTime: lastRotatedTime,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *AuthorizationInfo) autorestAuthorizer() (autorest.Authorizer, error) {
|
||||
credsConfig := auth.NewClientCredentialsConfig(
|
||||
a.AuthParams.ClientId,
|
||||
a.AuthParams.SecretValue,
|
||||
a.AuthParams.TenantId,
|
||||
)
|
||||
authorizer, err := credsConfig.Authorizer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error authorizing to Azure: %w", err)
|
||||
}
|
||||
if authorizer == nil {
|
||||
return nil, errors.New("generated authorizer is nil")
|
||||
}
|
||||
return authorizer, nil
|
||||
}
|
||||
|
||||
func (a *AuthorizationInfo) populateObjectId(ctx context.Context, opt ...Option) error {
|
||||
if a.AuthParams.ClientObjectId != "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if a.AuthParams.ClientId == "" {
|
||||
return errors.New("client id not known at object id lookup time")
|
||||
}
|
||||
// First, find the appId
|
||||
aClient, err := getApplicationsClient(ctx, a)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application client: %w", err)
|
||||
}
|
||||
if aClient == nil {
|
||||
return errors.New("applications client is nil when getting object id")
|
||||
}
|
||||
apps, _, err := aClient.List(ctx, odata.Query{
|
||||
Filter: fmt.Sprintf("appId eq '%s'", a.AuthParams.ClientId),
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("error listing applications to find objId: %w", err)
|
||||
}
|
||||
if apps == nil {
|
||||
return errors.New("nil apps returned when listing to find objId")
|
||||
}
|
||||
if len(*apps) != 1 {
|
||||
return fmt.Errorf("unexpected number of apps found, expected 1, found %d", len(*apps))
|
||||
}
|
||||
a.AuthParams.ClientObjectId = *(*apps)[0].DirectoryObject.ID
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// rotateCredentials creates a new password, then uses a client with that new
|
||||
// password to revoke the old.
|
||||
//
|
||||
// NOTE: the underlying auth config will be modified to use the new credentials!
|
||||
func rotateCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (*msgraph.PasswordCredential, error) {
|
||||
if hConfig == nil {
|
||||
return nil, errors.New("empty auth config id")
|
||||
func rotateCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) (*msgraph.PasswordCredential, error) {
|
||||
if authzInfo == nil {
|
||||
return nil, errors.New("empty authz info")
|
||||
}
|
||||
if hConfig.SecretId == "" {
|
||||
if authzInfo.AuthParams.SecretId == "" {
|
||||
return nil, errors.New("missing original secret id")
|
||||
}
|
||||
|
||||
// Get the object ID here so we don't have to look it up twice
|
||||
objId, err := getObjectId(ctx, hConfig)
|
||||
if err != nil {
|
||||
// Ensure the object ID is set
|
||||
if err := authzInfo.populateObjectId(ctx); err != nil {
|
||||
return nil, fmt.Errorf("error fetching object id: %w", err)
|
||||
}
|
||||
|
||||
newCred, err := addCredential(ctx, hConfig, WithObjectId(objId))
|
||||
newCred, err := addCredential(ctx, authzInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error adding password: %w", err)
|
||||
}
|
||||
@@ -209,19 +180,23 @@ func rotateCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt .
|
||||
return nil, errors.New("new credential secret text is nil after adding")
|
||||
}
|
||||
|
||||
if err := removeCredential(ctx, hConfig, WithObjectId(objId)); err != nil {
|
||||
if err := removeCredential(ctx, authzInfo); err != nil {
|
||||
return nil, fmt.Errorf("error removing previous credential: %w", err)
|
||||
}
|
||||
return newCred, nil
|
||||
}
|
||||
|
||||
func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) (*msgraph.PasswordCredential, error) {
|
||||
objId, err := getObjectId(ctx, hConfig, opt...)
|
||||
if err != nil {
|
||||
func addCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) (*msgraph.PasswordCredential, error) {
|
||||
if authzInfo == nil {
|
||||
return nil, errors.New("empty authz info")
|
||||
}
|
||||
|
||||
// Ensure the object ID is set
|
||||
if err := authzInfo.populateObjectId(ctx); err != nil {
|
||||
return nil, fmt.Errorf("error fetching object id: %w", err)
|
||||
}
|
||||
|
||||
aClient, err := getApplicationsClient(ctx, hConfig)
|
||||
aClient, err := getApplicationsClient(ctx, authzInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting application client: %w", err)
|
||||
}
|
||||
@@ -231,7 +206,7 @@ func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...O
|
||||
|
||||
// Create the new password
|
||||
displayName := fmt.Sprintf("boundary-rotated-%s", time.Now().Format(time.RFC3339))
|
||||
newPass, _, err := aClient.AddPassword(ctx, objId, msgraph.PasswordCredential{
|
||||
newPass, _, err := aClient.AddPassword(ctx, authzInfo.AuthParams.ClientObjectId, msgraph.PasswordCredential{
|
||||
DisplayName: &displayName,
|
||||
})
|
||||
if err != nil {
|
||||
@@ -250,9 +225,9 @@ func addCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...O
|
||||
return newPass, nil
|
||||
}
|
||||
|
||||
func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt ...Option) error {
|
||||
objId, err := getObjectId(ctx, hConfig, opt...)
|
||||
if err != nil {
|
||||
func removeCredential(ctx context.Context, authzInfo *AuthorizationInfo, opt ...Option) error {
|
||||
// Ensure the object ID is set
|
||||
if err := authzInfo.populateObjectId(ctx); err != nil {
|
||||
return fmt.Errorf("error fetching object id: %w", err)
|
||||
}
|
||||
|
||||
@@ -261,12 +236,12 @@ func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt .
|
||||
return fmt.Errorf("error parsing ops: %w", err)
|
||||
}
|
||||
|
||||
secretId := hConfig.SecretId
|
||||
secretId := authzInfo.AuthParams.SecretId
|
||||
if opts.withSecretId != "" {
|
||||
secretId = opts.withSecretId
|
||||
}
|
||||
|
||||
aClient, err := getApplicationsClient(ctx, hConfig)
|
||||
aClient, err := getApplicationsClient(ctx, authzInfo)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting application client: %w", err)
|
||||
}
|
||||
@@ -274,7 +249,7 @@ func removeCredential(ctx context.Context, hConfig *WrappedHamiltonConfig, opt .
|
||||
return errors.New("applications client is nil when removing credential")
|
||||
}
|
||||
|
||||
_, err = aClient.RemovePassword(ctx, objId, secretId)
|
||||
_, err = aClient.RemovePassword(ctx, authzInfo.AuthParams.ClientObjectId, secretId)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error removing old password: %w", err)
|
||||
}
|
||||
|
||||
+4
-7
@@ -112,20 +112,17 @@ func getPublicIpAddressesClient(opt ...Option) (*network.PublicIPAddressesClient
|
||||
return &client, nil
|
||||
}
|
||||
|
||||
func getApplicationsClient(ctx context.Context, authConfig *WrappedHamiltonConfig) (*msgraph.ApplicationsClient, error) {
|
||||
if authConfig == nil {
|
||||
func getApplicationsClient(ctx context.Context, authzInfo *AuthorizationInfo) (*msgraph.ApplicationsClient, error) {
|
||||
if authzInfo == nil {
|
||||
return nil, errors.New("empty auth config id when fetching service principals client")
|
||||
}
|
||||
if authConfig.TenantID == "" {
|
||||
return nil, errors.New("empty tenant id in auth config when fetching service principals client")
|
||||
}
|
||||
|
||||
authorizer, err := authConfig.NewAuthorizer(ctx, hauth.MsGraph)
|
||||
authorizer, err := authzInfo.HamiltonConfig.NewAuthorizer(ctx, hauth.MsGraph)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching hauth authorizer when fetching service principals client: %w", err)
|
||||
}
|
||||
|
||||
client := msgraph.NewApplicationsClient(authConfig.TenantID)
|
||||
client := msgraph.NewApplicationsClient(authzInfo.AuthParams.TenantId)
|
||||
client.BaseClient.Authorizer = authorizer
|
||||
|
||||
return client, nil
|
||||
|
||||
+12
-21
@@ -26,16 +26,15 @@ type Option func(*options) error
|
||||
|
||||
// options = how options are represented
|
||||
type options struct {
|
||||
withHostCatalogAttrs *structpb.Struct
|
||||
withPersistedData *structpb.Struct
|
||||
withSubscriptionId string
|
||||
withAuthorizer autorest.Authorizer
|
||||
withUserAgent string
|
||||
withBaseUrl string
|
||||
withWrappedHamiltonConfig *WrappedHamiltonConfig
|
||||
withClientId string
|
||||
withObjectId string
|
||||
withSecretId string
|
||||
withHostCatalogAttrs *structpb.Struct
|
||||
withPersistedData *structpb.Struct
|
||||
withSubscriptionId string
|
||||
withAuthorizer autorest.Authorizer
|
||||
withUserAgent string
|
||||
withBaseUrl string
|
||||
withAuthorizationInfo *AuthorizationInfo
|
||||
withClientId string
|
||||
withSecretId string
|
||||
}
|
||||
|
||||
func getDefaultOptions() options {
|
||||
@@ -100,10 +99,10 @@ func WithBaseUrl(with string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithWrappedHamiltonConfig contains the hamilton package authentication config to use
|
||||
func WithWrappedHamiltonConfig(with *WrappedHamiltonConfig) Option {
|
||||
// WithAuthorizationInfo contains the authorization information to use
|
||||
func WithAuthorizationInfo(with *AuthorizationInfo) Option {
|
||||
return func(o *options) error {
|
||||
o.withWrappedHamiltonConfig = with
|
||||
o.withAuthorizationInfo = with
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -116,14 +115,6 @@ func WithClientId(with string) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// WithObjectId contains the object ID to use
|
||||
func WithObjectId(with string) Option {
|
||||
return func(o *options) error {
|
||||
o.withObjectId = with
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSecretId contains the secret ID to use
|
||||
func WithSecretId(with string) Option {
|
||||
return func(o *options) error {
|
||||
|
||||
+31
-29
@@ -9,7 +9,7 @@ import (
|
||||
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/network/mgmt/network"
|
||||
"github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/resources"
|
||||
"github.com/kr/pretty"
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"google.golang.org/protobuf/types/known/structpb"
|
||||
|
||||
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/hostcatalogs"
|
||||
@@ -27,11 +27,11 @@ var (
|
||||
)
|
||||
|
||||
func rotateCredFromCallback(ctx context.Context, catalog *hostcatalogs.HostCatalog) (*pb.HostCatalogPersisted, error) {
|
||||
authConfig, err := getWrappedHamiltonConfig(catalog, nil)
|
||||
authzInfo, err := getAuthorizationInfo(catalog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting hauth config when creating catalog: %w", err)
|
||||
}
|
||||
newPass, err := rotateCredential(ctx, authConfig)
|
||||
newPass, err := rotateCredential(ctx, authzInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error rotating credentials when creating catalog: %w", err)
|
||||
}
|
||||
@@ -67,14 +67,12 @@ func (p *AzurePlugin) OnCreateCatalog(ctx context.Context, req *pb.OnCreateCatal
|
||||
// Check if rotation is skipped. Always prefer attempting rotation unless we
|
||||
// explicitly are told not to
|
||||
var skipRotation bool
|
||||
if attrs := catalog.GetAttributes(); attrs != nil {
|
||||
if fields := attrs.GetFields(); fields != nil {
|
||||
if field := fields[constDisableCredentialRotation]; field != nil {
|
||||
if field.GetBoolValue() {
|
||||
skipRotation = true
|
||||
}
|
||||
}
|
||||
if catalog.GetAttributes() != nil {
|
||||
var attrs Attributes
|
||||
if err := mapstructure.Decode(catalog.GetAttributes().AsMap(), &attrs); err != nil {
|
||||
return nil, fmt.Errorf("error decoding catalog attributes: %w", err)
|
||||
}
|
||||
skipRotation = attrs.DisableCredentialRotation
|
||||
}
|
||||
if skipRotation {
|
||||
return &pb.OnCreateCatalogResponse{
|
||||
@@ -117,13 +115,13 @@ func (p *AzurePlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatal
|
||||
sanitizeSecretsFields(secrets)
|
||||
|
||||
// If the old credential had been rotated, attempt to delete the old
|
||||
// credential using itself; if it fails, proceed anyways, since in theory
|
||||
// only Boundary knows this value so simply forgetting it is safe enough (if
|
||||
// not clean).
|
||||
// credentials using the new ones; if it fails, proceed anyways, since in
|
||||
// theory only Boundary knows this value so simply forgetting it is safe
|
||||
// enough (if not clean).
|
||||
var field *structpb.Value
|
||||
var pdFields map[string]*structpb.Value
|
||||
var err error
|
||||
var authConfig *WrappedHamiltonConfig
|
||||
var authzInfo *AuthorizationInfo
|
||||
|
||||
persisted := req.GetPersisted()
|
||||
if persisted == nil {
|
||||
@@ -141,12 +139,13 @@ func (p *AzurePlugin) OnUpdateCatalog(ctx context.Context, req *pb.OnUpdateCatal
|
||||
goto after_revocation
|
||||
}
|
||||
// If the field exists, it is a timestamp here indicating last rotation time
|
||||
authConfig, err = getWrappedHamiltonConfig(currentCatalog, req.GetPersisted())
|
||||
currentCatalog.Secrets = req.GetPersisted().Secrets
|
||||
authzInfo, err = getAuthorizationInfo(currentCatalog)
|
||||
if err != nil {
|
||||
// TODO: warn in some way?
|
||||
goto after_revocation
|
||||
}
|
||||
if err := removeCredential(ctx, authConfig); err != nil {
|
||||
if err := removeCredential(ctx, authzInfo); err != nil {
|
||||
// TODO: warn
|
||||
goto after_revocation
|
||||
}
|
||||
@@ -199,14 +198,16 @@ func (p *AzurePlugin) OnDeleteCatalog(ctx context.Context, req *pb.OnDeleteCatal
|
||||
return nil, nil
|
||||
}
|
||||
// The field existing means it is a string containing the last time it was
|
||||
// rotated. Delete the credential using itself.
|
||||
authConfig, err := getWrappedHamiltonConfig(req.GetCatalog(), req.GetPersisted())
|
||||
// rotated. Delete the credential using current creds.
|
||||
catalog := req.GetCatalog()
|
||||
catalog.Secrets = req.GetPersisted().GetSecrets()
|
||||
authzInfo, err := getAuthorizationInfo(catalog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting auth config on catalog deletion: %w", err)
|
||||
}
|
||||
if err := removeCredential(ctx, authConfig); err != nil {
|
||||
if err := removeCredential(ctx, authzInfo); err != nil {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error revoking credential on catalog deletion: %w, authConfig is %s", err, pretty.Sprint(authConfig))
|
||||
return nil, fmt.Errorf("error revoking credential on catalog deletion: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,16 +244,17 @@ func (p *AzurePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (
|
||||
}
|
||||
|
||||
// Create an authorizer
|
||||
var secrets *structpb.Struct
|
||||
if req.GetPersisted() != nil {
|
||||
secrets = req.GetPersisted().GetSecrets()
|
||||
}
|
||||
subscriptionId, authorizer, err := fetchAuthorizer(catalog, secrets)
|
||||
catalog.Secrets = req.GetPersisted().GetSecrets()
|
||||
authzInfo, err := getAuthorizationInfo(catalog)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching subscription id and authorizer: %w", err)
|
||||
return nil, fmt.Errorf("error fetching authorizationInfo: %w", err)
|
||||
}
|
||||
if subscriptionId == "" {
|
||||
return nil, errors.New("subscription id not discovered")
|
||||
if authzInfo == nil {
|
||||
return nil, errors.New("authorization info is nil")
|
||||
}
|
||||
authorizer, err := authzInfo.autorestAuthorizer()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error fetching autorest authorizer: %w", err)
|
||||
}
|
||||
if authorizer == nil {
|
||||
return nil, errors.New("fetched authorizer is nil")
|
||||
@@ -260,7 +262,7 @@ func (p *AzurePlugin) ListHosts(ctx context.Context, req *pb.ListHostsRequest) (
|
||||
|
||||
commonOpts := make([]Option, 0, 3)
|
||||
commonOpts = append(commonOpts,
|
||||
WithSubscriptionId(subscriptionId),
|
||||
WithSubscriptionId(authzInfo.AuthParams.SubscriptionId),
|
||||
WithAuthorizer(authorizer),
|
||||
)
|
||||
|
||||
|
||||
+20
-26
@@ -51,7 +51,7 @@ func TestOnCreateOnDeleteCatalog(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
p := &AzurePlugin{}
|
||||
hostCatalog, _ := testGetHostStructs(t)
|
||||
objId, initialAuthConfig, initialCred, cleanup := testCreateInitialSecret(t, hostCatalog)
|
||||
initialAuthConfig, initialCred, cleanup := testCreateInitialSecret(t, hostCatalog)
|
||||
defer cleanup()
|
||||
|
||||
// At this point, remove secrets corresponding to those read in by
|
||||
@@ -106,7 +106,6 @@ func TestOnCreateOnDeleteCatalog(t *testing.T) {
|
||||
_ = removeCredential(
|
||||
ctx,
|
||||
&deferAuthConfig,
|
||||
WithObjectId(objId),
|
||||
)
|
||||
}()
|
||||
|
||||
@@ -123,8 +122,9 @@ func TestOnCreateOnDeleteCatalog(t *testing.T) {
|
||||
// waitForCreds(t, &waitForCredsAuthConfig, false)
|
||||
|
||||
// Make sure the new creds work, then sleep
|
||||
waitForCredsAuthConfig.SecretId = newPdFields[constSecretId].GetStringValue()
|
||||
waitForCredsAuthConfig.ClientSecret = newPdFields[constSecretValue].GetStringValue()
|
||||
waitForCredsAuthConfig.AuthParams.SecretId = newPdFields[constSecretId].GetStringValue()
|
||||
waitForCredsAuthConfig.AuthParams.SecretValue = newPdFields[constSecretValue].GetStringValue()
|
||||
waitForCredsAuthConfig.HamiltonConfig.ClientSecret = newPdFields[constSecretValue].GetStringValue()
|
||||
waitForCreds(t, &waitForCredsAuthConfig, true)
|
||||
time.Sleep(10 * time.Minute)
|
||||
|
||||
@@ -139,8 +139,8 @@ func TestOnCreateOnDeleteCatalog(t *testing.T) {
|
||||
|
||||
// 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,
|
||||
constSecretValue: waitForCredsAuthConfig.HamiltonConfig.ClientSecret,
|
||||
constSecretId: waitForCredsAuthConfig.AuthParams.SecretId,
|
||||
})
|
||||
cdResp, err = p.OnDeleteCatalog(ctx, &pb.OnDeleteCatalogRequest{
|
||||
Catalog: hostCatalog,
|
||||
@@ -155,7 +155,7 @@ func TestOnCreateOnDeleteCatalog(t *testing.T) {
|
||||
// waitForCreds(t, &waitForCredsAuthConfig, false)
|
||||
}
|
||||
|
||||
func waitForCreds(t *testing.T, hConfig *WrappedHamiltonConfig, shouldWork bool) auth.Authorizer {
|
||||
func waitForCreds(t *testing.T, authzInfo *AuthorizationInfo, shouldWork bool) auth.Authorizer {
|
||||
t.Helper()
|
||||
require := require.New(t)
|
||||
ctx := context.Background()
|
||||
@@ -164,9 +164,9 @@ func waitForCreds(t *testing.T, hConfig *WrappedHamiltonConfig, shouldWork bool)
|
||||
environments.Global,
|
||||
auth.MsGraph,
|
||||
auth.TokenVersion2,
|
||||
hConfig.TenantID,
|
||||
hConfig.ClientID,
|
||||
hConfig.ClientSecret)
|
||||
authzInfo.AuthParams.TenantId,
|
||||
authzInfo.AuthParams.ClientId,
|
||||
authzInfo.AuthParams.SecretValue)
|
||||
require.NoError(err)
|
||||
|
||||
for {
|
||||
@@ -230,45 +230,39 @@ func testGetHostStructs(t *testing.T) (*hostcatalogs.HostCatalog, []*hostsets.Ho
|
||||
// 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()) {
|
||||
func testCreateInitialSecret(t *testing.T, hostCatalog *hostcatalogs.HostCatalog) (*AuthorizationInfo, *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()))
|
||||
authzInfo, err := getAuthorizationInfo(hostCatalog)
|
||||
require.NoError(err)
|
||||
|
||||
// Get the credential
|
||||
initialCred, err = addCredential(ctx, authConfig, WithObjectId(objId))
|
||||
initialCred, err = addCredential(ctx, authzInfo)
|
||||
require.NoError(err)
|
||||
require.NotNil(initialCred)
|
||||
require.NotNil(initialCred.KeyId)
|
||||
require.NotNil(initialCred.SecretText)
|
||||
|
||||
origAuthConfig := *authConfig
|
||||
origAuthConfig := *authzInfo
|
||||
origAuthConfig.AuthParams.SecretId = *initialCred.KeyId
|
||||
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)
|
||||
authzInfo.AuthParams.SecretId = *initialCred.KeyId
|
||||
authzInfo.AuthParams.SecretValue = *initialCred.SecretText
|
||||
authzInfo.HamiltonConfig.ClientSecret = *initialCred.SecretText
|
||||
waitForCreds(t, authzInfo, true)
|
||||
|
||||
return objId, authConfig, initialCred, cleanup
|
||||
return authzInfo, initialCred, cleanup
|
||||
}
|
||||
Reference in new issue
Block a user