From f67d1698b5c5eb47c85e121da10ddc56b7a05b9e Mon Sep 17 00:00:00 2001 From: Grace Rehn Date: Thu, 28 Nov 2024 14:48:45 +1000 Subject: [PATCH 1/4] feat: Add generic oidc account --- pkg/accounts/account.go | 2 +- pkg/accounts/account_types.go | 1 + pkg/accounts/account_utilities.go | 10 ++++ pkg/accounts/accounts.go | 7 +++ pkg/accounts/generic_oidc_account.go | 51 ++++++++++++++++ pkg/accounts/generic_oidc_account_test.go | 73 +++++++++++++++++++++++ 6 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 pkg/accounts/generic_oidc_account.go create mode 100644 pkg/accounts/generic_oidc_account_test.go diff --git a/pkg/accounts/account.go b/pkg/accounts/account.go index be9c9399..95cc6fb6 100644 --- a/pkg/accounts/account.go +++ b/pkg/accounts/account.go @@ -31,7 +31,7 @@ type IAccount interface { // account is the embedded struct used for all accounts. type account struct { - AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOidc AmazonWebServicesAccount AmazonWebServicesOidcAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token"` + AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOidc AmazonWebServicesAccount AmazonWebServicesOidcAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token GenericOidcAccount"` Description string `json:"Description,omitempty"` EnvironmentIDs []string `json:"EnvironmentIds,omitempty"` Name string `json:"Name" validate:"required,notblank,notall"` diff --git a/pkg/accounts/account_types.go b/pkg/accounts/account_types.go index 51ee5467..0491870c 100644 --- a/pkg/accounts/account_types.go +++ b/pkg/accounts/account_types.go @@ -9,6 +9,7 @@ const ( AccountTypeAzureOIDC = AccountType("AzureOidc") AccountTypeAwsOIDC = AccountType("AmazonWebServicesOidcAccount") AccountTypeAzureSubscription = AccountType("AzureSubscription") + AccountTypeGenericOIDCAccount = AccountType("GenericOidcAccount") AccountTypeGoogleCloudPlatformAccount = AccountType("GoogleCloudAccount") AccountTypeSSHKeyPair = AccountType("SshKeyPair") AccountTypeToken = AccountType("Token") diff --git a/pkg/accounts/account_utilities.go b/pkg/accounts/account_utilities.go index 9587873e..46cc9b62 100644 --- a/pkg/accounts/account_utilities.go +++ b/pkg/accounts/account_utilities.go @@ -69,6 +69,16 @@ func ToAccount(accountResource *AccountResource) (IAccount, error) { azureSubscriptionAccount.ManagementEndpoint = accountResource.ManagementEndpoint azureSubscriptionAccount.StorageEndpointSuffix = accountResource.StorageEndpointSuffix account = azureSubscriptionAccount + case AccountTypeGenericOIDCAccount: + genericOIDCAccount, err := NewGenericOIDCAccount(accountResource.GetName()) + if err != nil { + return nil, err + } + genericOIDCAccount.Audience = accountResource.Audience + genericOIDCAccount.DeploymentSubjectKeys = accountResource.DeploymentSubjectKeys + genericOIDCAccount.AccountTestSubjectKeys = accountResource.AccountTestSubjectKeys + genericOIDCAccount.HealthCheckSubjectKeys = accountResource.HealthCheckSubjectKeys + account = genericOIDCAccount case AccountTypeGoogleCloudPlatformAccount: googleCloudPlatformAccount, err := NewGoogleCloudPlatformAccount(accountResource.GetName(), accountResource.JsonKey) if err != nil { diff --git a/pkg/accounts/accounts.go b/pkg/accounts/accounts.go index 8956bfb2..dc665150 100644 --- a/pkg/accounts/accounts.go +++ b/pkg/accounts/accounts.go @@ -106,6 +106,13 @@ func (a *Accounts) UnmarshalJSON(b []byte) error { return err } a.Items = append(a.Items, azureSubscriptionAccount) + case AccountTypeGenericOIDCAccount: + var genericOIDCAccount *GenericOIDCAccount + err := json.Unmarshal(*account, &genericOIDCAccount) + if err != nil { + return err + } + a.Items = append(a.Items, genericOIDCAccount) case AccountTypeGoogleCloudPlatformAccount: var googleCloudAccount *GoogleCloudPlatformAccount err := json.Unmarshal(*account, &googleCloudAccount) diff --git a/pkg/accounts/generic_oidc_account.go b/pkg/accounts/generic_oidc_account.go new file mode 100644 index 00000000..98203623 --- /dev/null +++ b/pkg/accounts/generic_oidc_account.go @@ -0,0 +1,51 @@ +package accounts + +import ( + "github.com/OctopusDeploy/go-octopusdeploy/v2/internal" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/validation" + "github.com/go-playground/validator/v10" + "github.com/go-playground/validator/v10/non-standard/validators" +) + +// GenericOIDCAccount represents a Generic OIDC account. +type GenericOIDCAccount struct { + Audience string `json:"Audience,omitempty"` + DeploymentSubjectKeys []string `json:"DeploymentSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space environment project tenant runbook account type'"` + HealthCheckSubjectKeys []string `json:"HealthCheckSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space account target type'"` + AccountTestSubjectKeys []string `json:"AccountTestSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space account type'"` + + account +} + +// NewGenericOIDCAccount creates and initializes a Generic OIDC account. +func NewGenericOIDCAccount(name string) (*GenericOIDCAccount, error) { + if internal.IsEmpty(name) { + return nil, internal.CreateRequiredParameterIsEmptyOrNilError("name") + } + + account := GenericOIDCAccount{ + account: *newAccount(name, AccountTypeGenericOIDCAccount), + } + + // validate to ensure that all expectations are met + err := account.Validate() + if err != nil { + return nil, err + } + + return &account, nil +} + +// Validate checks the state of this account and returns an error if invalid. +func (a *GenericOIDCAccount) Validate() error { + v := validator.New() + err := v.RegisterValidation("notblank", validators.NotBlank) + if err != nil { + return err + } + err = v.RegisterValidation("notall", validation.NotAll) + if err != nil { + return err + } + return v.Struct(a) +} diff --git a/pkg/accounts/generic_oidc_account_test.go b/pkg/accounts/generic_oidc_account_test.go new file mode 100644 index 00000000..2229691f --- /dev/null +++ b/pkg/accounts/generic_oidc_account_test.go @@ -0,0 +1,73 @@ +package accounts + +import ( + "github.com/OctopusDeploy/go-octopusdeploy/v2/internal" + "github.com/stretchr/testify/require" + "testing" +) + +func TestGenericOIDCAccount(t *testing.T) { + name := internal.GetRandomName() + audience := "api://default" + deploymentSubjectKeys := []string{"space", "project", "tenant", "environment"} + healthCheckSubjectKeys := []string{"space", "target"} + accountTestSubjectKeys := []string{"space", "account"} + invalidDeploymentSubjectKeys := []string{"space", "target"} + invalidHealthCheckSubjectKeys := []string{"space", "project"} + invalidAccountTestSubjectKeys := []string{"space", "project"} + + testCases := []struct { + TestName string + IsError bool + Name string + Audience string + DeploymentSubjectKeys []string + HealthCheckSubjectKeys []string + AccountTestSubjectKeys []string + }{ + {"Valid", false, name, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, + {"EmptyName", true, "", audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, + {"NilSubjectKeys", false, name, "", nil, nil, nil}, + {"InvalidDeploymentSubjectKeys", true, name, "", invalidDeploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, + {"InvalidHealthCheckSubjectKeys", true, name, "", deploymentSubjectKeys, invalidHealthCheckSubjectKeys, invalidAccountTestSubjectKeys}, + {"InvalidAccountTestSubjectKeys", true, name, "", deploymentSubjectKeys, healthCheckSubjectKeys, invalidAccountTestSubjectKeys}, + } + + for _, tc := range testCases { + t.Run(tc.TestName, func(t *testing.T) { + genericOIDCAccount := &GenericOIDCAccount{ + Audience: tc.Audience, + DeploymentSubjectKeys: tc.DeploymentSubjectKeys, + HealthCheckSubjectKeys: tc.HealthCheckSubjectKeys, + AccountTestSubjectKeys: tc.AccountTestSubjectKeys, + } + genericOIDCAccount.AccountType = AccountTypeGenericOIDCAccount + genericOIDCAccount.Name = tc.Name + if tc.IsError { + require.Error(t, genericOIDCAccount.Validate()) + } else { + require.NoError(t, genericOIDCAccount.Validate()) + + require.Equal(t, AccountTypeGenericOIDCAccount, genericOIDCAccount.GetAccountType()) + require.Equal(t, tc.Name, genericOIDCAccount.GetName()) + } + genericOIDCAccount.SetName(tc.Name) + if tc.IsError { + require.Error(t, genericOIDCAccount.Validate()) + } else { + require.NoError(t, genericOIDCAccount.Validate()) + require.Equal(t, tc.Name, genericOIDCAccount.GetName()) + } + }) + } +} + +func TestGenericOIDCAccountNew(t *testing.T) { + name := internal.GetRandomName() + + account, err := NewGenericOIDCAccount(name) + + require.NotNil(t, account) + require.NoError(t, err) + require.NoError(t, account.Validate()) +} From 30bb66b4d2e401b927458062ee0008715438eeaa Mon Sep 17 00:00:00 2001 From: Grace Rehn Date: Mon, 2 Dec 2024 11:18:06 +1000 Subject: [PATCH 2/4] chore: Add generic oidc account to account type --- pkg/accounts/account.go | 2 +- pkg/accounts/account_resource.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/accounts/account.go b/pkg/accounts/account.go index 95cc6fb6..932a6e59 100644 --- a/pkg/accounts/account.go +++ b/pkg/accounts/account.go @@ -31,7 +31,7 @@ type IAccount interface { // account is the embedded struct used for all accounts. type account struct { - AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOidc AmazonWebServicesAccount AmazonWebServicesOidcAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token GenericOidcAccount"` + AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOidc AmazonWebServicesAccount AmazonWebServicesOidcAccount AmazonWebServicesRoleAccount GoogleCloudAccount GenericOidcAccount Token"` Description string `json:"Description,omitempty"` EnvironmentIDs []string `json:"EnvironmentIds,omitempty"` Name string `json:"Name" validate:"required,notblank,notall"` diff --git a/pkg/accounts/account_resource.go b/pkg/accounts/account_resource.go index 77217a11..520aefa2 100644 --- a/pkg/accounts/account_resource.go +++ b/pkg/accounts/account_resource.go @@ -12,7 +12,7 @@ import ( // username/password, tokens, Azure and AWS credentials, and SSH key pairs. type AccountResource struct { AccessKey string `json:"AccessKey,omitempty"` - AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOidc AmazonWebServicesAccount AmazonWebServicesRoleAccount AmazonWebServicesOidcAccount GoogleCloudAccount Token"` + AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOidc AmazonWebServicesAccount AmazonWebServicesRoleAccount AmazonWebServicesOidcAccount GoogleCloudAccount GenericOidcAccount Token"` ApplicationID *uuid.UUID `json:"ClientId,omitempty"` ApplicationPassword *core.SensitiveValue `json:"Password,omitempty"` AuthenticationEndpoint string `json:"ActiveDirectoryEndpointBaseUri,omitempty"` From 075a4f06c2276df3019aaae2c3d17fe378091359 Mon Sep 17 00:00:00 2001 From: Grace Rehn Date: Mon, 2 Dec 2024 12:46:36 +1000 Subject: [PATCH 3/4] chore: remove subject keys which are not required --- pkg/accounts/account_utilities.go | 2 -- pkg/accounts/generic_oidc_account.go | 6 ++--- pkg/accounts/generic_oidc_account_test.go | 32 ++++++++--------------- 3 files changed, 13 insertions(+), 27 deletions(-) diff --git a/pkg/accounts/account_utilities.go b/pkg/accounts/account_utilities.go index 46cc9b62..72e2f239 100644 --- a/pkg/accounts/account_utilities.go +++ b/pkg/accounts/account_utilities.go @@ -76,8 +76,6 @@ func ToAccount(accountResource *AccountResource) (IAccount, error) { } genericOIDCAccount.Audience = accountResource.Audience genericOIDCAccount.DeploymentSubjectKeys = accountResource.DeploymentSubjectKeys - genericOIDCAccount.AccountTestSubjectKeys = accountResource.AccountTestSubjectKeys - genericOIDCAccount.HealthCheckSubjectKeys = accountResource.HealthCheckSubjectKeys account = genericOIDCAccount case AccountTypeGoogleCloudPlatformAccount: googleCloudPlatformAccount, err := NewGoogleCloudPlatformAccount(accountResource.GetName(), accountResource.JsonKey) diff --git a/pkg/accounts/generic_oidc_account.go b/pkg/accounts/generic_oidc_account.go index 98203623..8f95b8a3 100644 --- a/pkg/accounts/generic_oidc_account.go +++ b/pkg/accounts/generic_oidc_account.go @@ -9,10 +9,8 @@ import ( // GenericOIDCAccount represents a Generic OIDC account. type GenericOIDCAccount struct { - Audience string `json:"Audience,omitempty"` - DeploymentSubjectKeys []string `json:"DeploymentSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space environment project tenant runbook account type'"` - HealthCheckSubjectKeys []string `json:"HealthCheckSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space account target type'"` - AccountTestSubjectKeys []string `json:"AccountTestSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space account type'"` + Audience string `json:"Audience,omitempty"` + DeploymentSubjectKeys []string `json:"DeploymentSubjectKeys,omitempty" validate:"omitempty,dive,oneof=space environment project tenant runbook account type'"` account } diff --git a/pkg/accounts/generic_oidc_account_test.go b/pkg/accounts/generic_oidc_account_test.go index 2229691f..d73bc617 100644 --- a/pkg/accounts/generic_oidc_account_test.go +++ b/pkg/accounts/generic_oidc_account_test.go @@ -10,36 +10,26 @@ func TestGenericOIDCAccount(t *testing.T) { name := internal.GetRandomName() audience := "api://default" deploymentSubjectKeys := []string{"space", "project", "tenant", "environment"} - healthCheckSubjectKeys := []string{"space", "target"} - accountTestSubjectKeys := []string{"space", "account"} invalidDeploymentSubjectKeys := []string{"space", "target"} - invalidHealthCheckSubjectKeys := []string{"space", "project"} - invalidAccountTestSubjectKeys := []string{"space", "project"} testCases := []struct { - TestName string - IsError bool - Name string - Audience string - DeploymentSubjectKeys []string - HealthCheckSubjectKeys []string - AccountTestSubjectKeys []string + TestName string + IsError bool + Name string + Audience string + DeploymentSubjectKeys []string }{ - {"Valid", false, name, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, - {"EmptyName", true, "", audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, - {"NilSubjectKeys", false, name, "", nil, nil, nil}, - {"InvalidDeploymentSubjectKeys", true, name, "", invalidDeploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, - {"InvalidHealthCheckSubjectKeys", true, name, "", deploymentSubjectKeys, invalidHealthCheckSubjectKeys, invalidAccountTestSubjectKeys}, - {"InvalidAccountTestSubjectKeys", true, name, "", deploymentSubjectKeys, healthCheckSubjectKeys, invalidAccountTestSubjectKeys}, + {"Valid", false, name, audience, deploymentSubjectKeys}, + {"EmptyName", true, "", audience, deploymentSubjectKeys}, + {"NilSubjectKeys", false, name, "", nil}, + {"InvalidDeploymentSubjectKeys", true, name, "", invalidDeploymentSubjectKeys}, } for _, tc := range testCases { t.Run(tc.TestName, func(t *testing.T) { genericOIDCAccount := &GenericOIDCAccount{ - Audience: tc.Audience, - DeploymentSubjectKeys: tc.DeploymentSubjectKeys, - HealthCheckSubjectKeys: tc.HealthCheckSubjectKeys, - AccountTestSubjectKeys: tc.AccountTestSubjectKeys, + Audience: tc.Audience, + DeploymentSubjectKeys: tc.DeploymentSubjectKeys, } genericOIDCAccount.AccountType = AccountTypeGenericOIDCAccount genericOIDCAccount.Name = tc.Name From 726a1dc47be57b962dcfece4c5fd1859edc58bb8 Mon Sep 17 00:00:00 2001 From: Grace Rehn Date: Tue, 3 Dec 2024 15:22:54 +1000 Subject: [PATCH 4/4] fix: add AccountTypeGenericOIDCAccount to account resource creation --- pkg/accounts/account_utilities.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/accounts/account_utilities.go b/pkg/accounts/account_utilities.go index 72e2f239..7a387509 100644 --- a/pkg/accounts/account_utilities.go +++ b/pkg/accounts/account_utilities.go @@ -183,6 +183,10 @@ func ToAccountResource(account IAccount) (*AccountResource, error) { accountResource.ManagementEndpoint = azureSubscriptionAccount.ManagementEndpoint accountResource.StorageEndpointSuffix = azureSubscriptionAccount.StorageEndpointSuffix accountResource.SubscriptionID = azureSubscriptionAccount.SubscriptionID + case AccountTypeGenericOIDCAccount: + genericOidcAccount := account.(*GenericOIDCAccount) + accountResource.DeploymentSubjectKeys = genericOidcAccount.DeploymentSubjectKeys + accountResource.Audience = genericOidcAccount.Audience case AccountTypeGoogleCloudPlatformAccount: googleCloudPlatformAccount := account.(*GoogleCloudPlatformAccount) accountResource.JsonKey = googleCloudPlatformAccount.JsonKey