forked from OctopusDeploy/go-octopusdeploy
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Azure OIDC Support (OctopusDeploy#219)
Co-authored-by: Ben Pearce <[email protected]>
- Loading branch information
1 parent
e5978db
commit 8e99237
Showing
13 changed files
with
343 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package examples | ||
|
||
import ( | ||
"fmt" | ||
"net/url" | ||
|
||
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/accounts" | ||
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" | ||
uuid "github.com/google/uuid" | ||
) | ||
|
||
func CreateAzureOIDCExample() { | ||
var ( | ||
apiKey string = "API-YOUR_API_KEY" | ||
octopusURL string = "https://your_octopus_url" | ||
spaceID string = "space-id" | ||
|
||
// Azure-specific values | ||
azureApplicationID uuid.UUID = uuid.MustParse("client-UUID") | ||
azureSubscriptionID uuid.UUID = uuid.MustParse("subscription-UUID") | ||
azureTenantID uuid.UUID = uuid.MustParse("tenant-UUID") | ||
|
||
// Subject claims | ||
deploymentSubjectKeys []string = nil | ||
healthCheckSubjectKeys []string = nil | ||
accountTestSubjectKeys []string = nil | ||
|
||
// Other claims | ||
audience string = "" | ||
|
||
// account values | ||
accountName string = "Azure Account" | ||
accountDescription string = "My Azure Account" | ||
tenantTags []string = nil | ||
tenantIDs []string = nil | ||
environmentIDs []string = nil | ||
) | ||
|
||
apiURL, err := url.Parse(octopusURL) | ||
if err != nil { | ||
_ = fmt.Errorf("error parsing URL for Octopus API: %v", err) | ||
return | ||
} | ||
|
||
client, err := client.NewClient(nil, apiURL, apiKey, spaceID) | ||
if err != nil { | ||
_ = fmt.Errorf("error creating API client: %v", err) | ||
return | ||
} | ||
|
||
azureAccount, err := accounts.NewAzureOIDCAccount(accountName, azureSubscriptionID, azureTenantID, azureApplicationID) | ||
if err != nil { | ||
_ = fmt.Errorf("error creating Azure service principal account: %v", err) | ||
return | ||
} | ||
|
||
// fill in claims | ||
azureAccount.DeploymentSubjectKeys = deploymentSubjectKeys | ||
azureAccount.HealthCheckSubjectKeys = healthCheckSubjectKeys | ||
azureAccount.AccountTestSubjectKeys = accountTestSubjectKeys | ||
azureAccount.Audience = audience | ||
|
||
// fill in account details | ||
azureAccount.Description = accountDescription | ||
azureAccount.TenantTags = tenantTags | ||
azureAccount.TenantIDs = tenantIDs | ||
azureAccount.EnvironmentIDs = environmentIDs | ||
|
||
// create account | ||
createdAccount, err := accounts.Add(client, azureAccount) | ||
if err != nil { | ||
_ = fmt.Errorf("error adding account: %v", err) | ||
} | ||
|
||
azureAccount = createdAccount.(*accounts.AzureOIDCAccount) | ||
|
||
// work with created account | ||
fmt.Printf("account created: (%s)\n", azureAccount.GetID()) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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" 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 | ||
} | ||
|
||
// NewAzureOIDCAccount creates and initializes an Azure OIDC 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, AccountTypeAzureOIDC), | ||
} | ||
|
||
// 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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,111 @@ | ||
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"} | ||
invalidDeploymentSubjectKeys := []string{"space", "target"} | ||
invalidHealthCheckSubjectKeys := []string{"space", "project"} | ||
invalidAccountTestSubjectKeys := []string{"space", "project"} | ||
|
||
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}, | ||
{"NilSubjectKeys", false, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, "", nil, nil, nil}, | ||
{"InvalidDeploymentSubjectKeys", true, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, "", invalidDeploymentSubjectKeys, healthCheckSubjectKeys, accountTestSubjectKeys}, | ||
{"InvalidHealthCheckSubjectKeys", true, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, "", deploymentSubjectKeys, invalidHealthCheckSubjectKeys, invalidAccountTestSubjectKeys}, | ||
{"InvalidAccountTestSubjectKeys", true, &applicationID, authenticationEndpoint, azureEnvironment, name, resourceManagerEndpoint, spaceID, &subscriptionID, tenantedDeploymentMode, &tenantID, "", deploymentSubjectKeys, healthCheckSubjectKeys, invalidAccountTestSubjectKeys}, | ||
} | ||
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 = AccountTypeAzureOIDC | ||
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, AccountTypeAzureOIDC, 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()) | ||
} |
Oops, something went wrong.