Skip to content

Commit

Permalink
add azure oidc account
Browse files Browse the repository at this point in the history
  • Loading branch information
veochen-octopus committed Nov 13, 2023
1 parent e5978db commit 6b43dda
Show file tree
Hide file tree
Showing 6 changed files with 176 additions and 2 deletions.
2 changes: 1 addition & 1 deletion pkg/accounts/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 AmazonWebServicesAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token"`
AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOIDC AmazonWebServicesAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token"`
Description string `json:"Description,omitempty"`
EnvironmentIDs []string `json:"EnvironmentIds,omitempty"`
Name string `json:"Name" validate:"required,notblank,notall"`
Expand Down
2 changes: 1 addition & 1 deletion pkg/accounts/account_resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 AmazonWebServicesAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token"`
AccountType AccountType `json:"AccountType" validate:"required,oneof=None UsernamePassword SshKeyPair AzureSubscription AzureServicePrincipal AzureOIDC AmazonWebServicesAccount AmazonWebServicesRoleAccount GoogleCloudAccount Token"`
ApplicationID *uuid.UUID `json:"ClientId,omitempty"`
ApplicationPassword *core.SensitiveValue `json:"Password,omitempty"`
AuthenticationEndpoint string `json:"ActiveDirectoryEndpointBaseUri,omitempty"`
Expand Down
1 change: 1 addition & 0 deletions pkg/accounts/account_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const (
AccountTypeNone = AccountType("None")
AccountTypeAmazonWebServicesAccount = AccountType("AmazonWebServicesAccount")
AccountTypeAzureServicePrincipal = AccountType("AzureServicePrincipal")
AccountTypeAzureOIDC = AccountType("AzureOIDC")
AccountTypeAzureSubscription = AccountType("AzureSubscription")
AccountTypeGoogleCloudPlatformAccount = AccountType("GoogleCloudAccount")
AccountTypeSSHKeyPair = AccountType("SshKeyPair")
Expand Down
7 changes: 7 additions & 0 deletions pkg/accounts/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,13 @@ func (a *Accounts) UnmarshalJSON(b []byte) error {
return err
}
a.Items = append(a.Items, azureSubscriptionAccount)
case AccountTypeAzureOIDC:
var azureOIDCAccount *AzureOIDCAccount
err := json.Unmarshal(*account, &azureOIDCAccount)
if err != nil {
return err
}
a.Items = append(a.Items, azureOIDCAccount)
case AccountTypeGoogleCloudPlatformAccount:
var googleCloudAccount *GoogleCloudPlatformAccount
err := json.Unmarshal(*account, &googleCloudAccount)
Expand Down
61 changes: 61 additions & 0 deletions pkg/accounts/azure_oidc_account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package accounts

import (
"github.com/OctopusDeploy/go-octopusdeploy/v2/internal"
validation "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/validation"
"github.com/go-playground/validator/v10"
"github.com/go-playground/validator/v10/non-standard/validators"
uuid "github.com/google/uuid"
)

// AzureOIDCAccount represents an Azure OIDC account.
type AzureOIDCAccount struct {
ApplicationID *uuid.UUID `json:"ClientId" validate:"required"`
AuthenticationEndpoint string `json:"ActiveDirectoryEndpointBaseUri,omitempty" validate:"required_with=AzureEnvironment,omitempty,uri"`
AzureEnvironment string `json:"AzureEnvironment,omitempty" validate:"omitempty,oneof=AzureCloud AzureChinaCloud AzureGermanCloud AzureUSGovernment"`
ResourceManagerEndpoint string `json:"ResourceManagementEndpointBaseUri" validate:"required_with=AzureEnvironment,omitempty,uri"`
SubscriptionID *uuid.UUID `json:"SubscriptionNumber" validate:"required"`
TenantID *uuid.UUID `json:"TenantId" validate:"required"`
Audience string `json:"Audience,omitempty"`
DeploymentSubjectKeys []string `json:"DeploymentSubjectKeys,omitempty"`
HealthCheckSubjectKeys []string `json:"HealthCheckSubjectKeys,omitempty"`
AccountTestSubjectKeys []string `json:"AccountTestSubjectKeys,omitempty"`

account
}

// NewAzureOIDCAccount creates and initializes an Azure service principal account.
func NewAzureOIDCAccount(name string, subscriptionID uuid.UUID, tenantID uuid.UUID, applicationID uuid.UUID) (*AzureOIDCAccount, error) {
if internal.IsEmpty(name) {
return nil, internal.CreateRequiredParameterIsEmptyOrNilError("name")
}

account := AzureOIDCAccount{
ApplicationID: &applicationID,
SubscriptionID: &subscriptionID,
TenantID: &tenantID,
account: *newAccount(name, AccountType("AzureOIDC")),
}

// 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 *AzureOIDCAccount) 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)
}
105 changes: 105 additions & 0 deletions pkg/accounts/azure_oidc_account_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package accounts

import (
"testing"

"github.com/OctopusDeploy/go-octopusdeploy/v2/internal"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
uuid "github.com/google/uuid"
"github.com/stretchr/testify/require"
)

func TestAzureOIDCAccount(t *testing.T) {
applicationID := uuid.New()
authenticationEndpoint := "https://login.microsoftonline.com/"
azureEnvironment := "AzureCloud"
invalidURI := "***"
name := internal.GetRandomName()
resourceManagerEndpoint := "https://management.azure.com/"
spaceID := "space-id"
subscriptionID := uuid.New()
tenantedDeploymentMode := core.TenantedDeploymentMode("Untenanted")
tenantID := uuid.New()
audience := "api://AzureADTokenExchange"
deploymentSubjectKeys := []string{"space", "project", "tenant", "environment"}
healthCheckSubjectKeys := []string{"space", "target"}
accountTestSubjectKeys := []string{"space", "account"}

testCases := []struct {
TestName string
IsError bool
ApplicationID *uuid.UUID
AuthenticationEndpoint string
AzureEnvironment string
Name string
ResourceManagerEndpoint string
SpaceID string
SubscriptionID *uuid.UUID
TenantedDeploymentMode core.TenantedDeploymentMode
TenantID *uuid.UUID
Audience string
DeploymentSubjectKeys []string
HealthCheckSubjectKeys []string
AccountTestSubjectKeys []string
}{
{"Valid", false, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"EmptyName", true, &applicationID, authenticationEndpoint, azureEnvironment, "", resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"WhitespaceName", true, &applicationID, authenticationEndpoint, azureEnvironment, " ", resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"EmptySpaceID", false, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, "", &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"WhitespaceSpaceID", false, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, " ", &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"NilApplicationID", true, nil, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"NilSubscriptionID", true, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, nil, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"NilTenantID", true, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, nil, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"InvalidAuthenticationEndpoint", true, &applicationID, invalidURI, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"InvalidResourceManagerEndpoint", true, &applicationID, authenticationEndpoint, azureEnvironment, name, invalidURI, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, audience, deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
{"EmptyAudience", false, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, "", deploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys},
}
for _, tc := range testCases {
t.Run(tc.TestName, func(t *testing.T) {
azureOIDCAccount := &AzureOIDCAccount{
ApplicationID: tc.ApplicationID,
AuthenticationEndpoint: tc.AuthenticationEndpoint,
AzureEnvironment: tc.AzureEnvironment,
ResourceManagerEndpoint: tc.ResourceManagerEndpoint,
SubscriptionID: tc.SubscriptionID,
TenantID: tc.TenantID,
Audience: tc.Audience,
DeploymentSubjectKeys: tc.DeploymentSubjectKeys,
HealthCheckSubjectKeys: tc.HealthCheckSubjectKeys,
AccountTestSubjectKeys: tc.AccountTestSubjectKeys,
}
azureOIDCAccount.AccountType = AccountType("AzureOIDC")
azureOIDCAccount.Name = tc.Name
azureOIDCAccount.SpaceID = tc.SpaceID
azureOIDCAccount.TenantedDeploymentMode = tc.TenantedDeploymentMode
if tc.IsError {
require.Error(t, azureOIDCAccount.Validate())
} else {
require.NoError(t, azureOIDCAccount.Validate())

require.Equal(t, AccountType("AzureOIDC"), azureOIDCAccount.GetAccountType())
require.Equal(t, tc.Name, azureOIDCAccount.GetName())
}
azureOIDCAccount.SetName(tc.Name)
if tc.IsError {
require.Error(t, azureOIDCAccount.Validate())
} else {
require.NoError(t, azureOIDCAccount.Validate())
require.Equal(t, tc.Name, azureOIDCAccount.GetName())
}
})
}
}

func TestAzureOIDCAccountNew(t *testing.T) {
applicationID := uuid.New()
name := internal.GetRandomName()
subscriptionID := uuid.New()
tenantID := uuid.New()

account, err := NewAzureOIDCAccount(name, subscriptionID, tenantID, applicationID)

require.NotNil(t, account)
require.NoError(t, err)
require.NoError(t, account.Validate())
}

0 comments on commit 6b43dda

Please sign in to comment.