Skip to content

Commit

Permalink
feat: add support for OIDC bearer tokens to auth the provider against…
Browse files Browse the repository at this point in the history
… Octopus API (#810)

* feat: add support for OIDC bearer tokens to auth TFP against Octopus API
* docs: add info about auth methods
  • Loading branch information
mjhilton authored Nov 11, 2024
1 parent 51ec30c commit cce0c64
Show file tree
Hide file tree
Showing 8 changed files with 223 additions and 32 deletions.
44 changes: 41 additions & 3 deletions docs/guides/2-provider-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ subcategory: "Guides"

## Example usage

### API Key

`main.tf`

```hcl
Expand All @@ -25,13 +27,49 @@ provider "octopusdeploy" {
}
```

### Access Token (via Environment Variable)
OIDC Access Tokens are short-lived and typically generated per-run of an automated pipeline, such as GitHub Actions.
If you use the Access Token approach, we recommend sourcing the token from environment variable.

The environment variable fallback values that the Terraform Provider search for correspond to the values that pipeline steps like our [GitHub Login action](https://github.com/OctopusDeploy/login?tab=readme-ov-file#outputs) set in the pipeline context, so the provider will automatically pick up the value from environment variable.

`main.tf`

```hcl
terraform {
required_providers {
octopusdeploy = {
source = OctopusDeployLabs/octopusdeploy
}
}
}
provider "octopusdeploy" {
space_id = "..."
}
```

## Schema

### Required
* `address` (String) The Octopus Deploy server URL. This can also be set using the `OCTOPUS_URL` environment variable.
* `api_key` (String) The Octopus Deploy server API key. This can also be set using the `OCTOPUS_APIKEY` environment variable.
* `address` (String) The Octopus Deploy server URL.

and one of either
* `api_key` (String) The Octopus Deploy server API key.

OR
* `access_token` (String) The OIDC Access Token from an OIDC exchange.

### Optional
* `space_id` (String) The ID of the space to create the resources in.

**If `space_id` is not specified the default space will be used.**
**If `space_id` is not specified the default space will be used.**

### Environment Variable fallback
The following priority order will be used to calculate the final value for these configuration items:

| Configuration Item | Priority Order |
|--------------------|--------------------------------------------------------------------------------------------------|
| `address` | 1. Provider Configuration Block <br /> 2. env: `OCTOPUS_URL` |
| `api_key` | 1. Provider Configuration Block <br /> 2. env: `OCTOPUS_APIKEY` <br /> 3. env: `OCTOPUS_API_KEY` |
| `access_token` | 1. Provider Configuration Block <br /> 2. env: `OCTOPUS_ACCESS_TOKEN` |
8 changes: 8 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ This provider is used to configure resources in Octopus Deploy. The provider mus

## Configuration

### Authentication Methods
The provider supports authenticating to an Octopus Server instance via either:
* API Key
* OIDC Access Token

These are mutually exclusive options - use either, not both. For backward compatibility, API Key will always be preferred over OIDC, when an API Key is present.

### Default Space

Octopus Deploy supports the concept of a Default Space. This is the first space that is automatically created on server setup. If you do not specify a Space when configuring the Octopus Deploy Terraform provider it will use the Default Space.
Expand Down Expand Up @@ -81,6 +88,7 @@ resource "octopusdeploy_environment" "Env3" {

### Optional

- `access_token` (String) The OIDC Access Token to use with the Octopus REST API
- `address` (String) The endpoint of the Octopus REST API
- `api_key` (String) The API key to use with the Octopus REST API
- `space_id` (String) The space ID to target
57 changes: 47 additions & 10 deletions octopusdeploy/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package octopusdeploy

import (
"fmt"
"net/url"

"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client"
Expand All @@ -10,19 +11,15 @@ import (

// Config holds Address and the APIKey of the Octopus Deploy server
type Config struct {
Address string
APIKey string
SpaceID string
Address string
APIKey string
AccessToken string
SpaceID string
}

// Client returns a new Octopus Deploy client
func (c *Config) Client() (*client.Client, diag.Diagnostics) {
apiURL, err := url.Parse(c.Address)
if err != nil {
return nil, diag.FromErr(err)
}

octopus, err := client.NewClient(nil, apiURL, c.APIKey, "")
octopus, err := getClientForDefaultSpace(c)
if err != nil {
return nil, diag.FromErr(err)
}
Expand All @@ -33,11 +30,51 @@ func (c *Config) Client() (*client.Client, diag.Diagnostics) {
return nil, diag.FromErr(err)
}

octopus, err = client.NewClient(nil, apiURL, c.APIKey, space.GetID())
octopus, err = getClientForSpace(c, space.GetID())
if err != nil {
return nil, diag.FromErr(err)
}
}

return octopus, nil
}

func getClientForDefaultSpace(c *Config) (*client.Client, error) {
return getClientForSpace(c, "")
}

func getClientForSpace(c *Config, spaceID string) (*client.Client, error) {
apiURL, err := url.Parse(c.Address)
if err != nil {
return nil, err
}

credential, err := getApiCredential(c)
if err != nil {
return nil, err
}

return client.NewClientWithCredentials(nil, apiURL, credential, spaceID, "TerraformProvider")
}

func getApiCredential(c *Config) (client.ICredential, error) {
if c.APIKey != "" {
credential, err := client.NewApiKey(c.APIKey)
if err != nil {
return nil, err
}

return credential, nil
}

if c.AccessToken != "" {
credential, err := client.NewAccessToken(c.AccessToken)
if err != nil {
return nil, err
}

return credential, nil
}

return nil, fmt.Errorf("either an APIKey or an AccessToken is required to connect to the Octopus Server instance")
}
13 changes: 10 additions & 3 deletions octopusdeploy/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,17 @@ func Provider() *schema.Provider {
Type: schema.TypeString,
},
"api_key": {
DefaultFunc: schema.EnvDefaultFunc("OCTOPUS_APIKEY", nil),
DefaultFunc: schema.MultiEnvDefaultFunc([]string{"OCTOPUS_APIKEY", "OCTOPUS_API_KEY"}, nil),
Description: "The API key to use with the Octopus REST API",
Optional: true,
Type: schema.TypeString,
},
"access_token": {
DefaultFunc: schema.EnvDefaultFunc("OCTOPUS_ACCESS_TOKEN", nil),
Description: "The OIDC Access Token to use with the Octopus REST API",
Optional: true,
Type: schema.TypeString,
},
"space_id": {
Description: "The space ID to target",
Optional: true,
Expand All @@ -92,8 +98,9 @@ func Provider() *schema.Provider {

func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) {
config := Config{
Address: d.Get("address").(string),
APIKey: d.Get("api_key").(string),
AccessToken: d.Get("access_token").(string),
Address: d.Get("address").(string),
APIKey: d.Get("api_key").(string),
}
if spaceID, ok := d.GetOk("space_id"); ok {
config.SpaceID = spaceID.(string)
Expand Down
65 changes: 55 additions & 10 deletions octopusdeploy_framework/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,17 @@ import (
)

type Config struct {
Address string
ApiKey string
SpaceID string
Client *client.Client
Address string
ApiKey string
AccessToken string
SpaceID string
Client *client.Client
}

func (c *Config) GetClient(ctx context.Context) error {
tflog.Debug(ctx, "GetClient")
apiURL, err := url.Parse(c.Address)
if err != nil {
return err
}

octopus, err := client.NewClient(nil, apiURL, c.ApiKey, "")
octopus, err := getClientForDefaultSpace(c, ctx)
if err != nil {
return err
}
Expand All @@ -36,7 +33,7 @@ func (c *Config) GetClient(ctx context.Context) error {
return err
}

octopus, err = client.NewClient(nil, apiURL, c.ApiKey, space.GetID())
octopus, err = getClientForSpace(c, ctx, space.GetID())
if err != nil {
return err
}
Expand All @@ -49,6 +46,54 @@ func (c *Config) GetClient(ctx context.Context) error {
return nil
}

func getClientForDefaultSpace(c *Config, ctx context.Context) (*client.Client, error) {
return getClientForSpace(c, ctx, "")
}

func getClientForSpace(c *Config, ctx context.Context, spaceID string) (*client.Client, error) {
apiURL, err := url.Parse(c.Address)
if err != nil {
return nil, err
}

credential, err := getApiCredential(c, ctx)
if err != nil {
return nil, err
}

return client.NewClientWithCredentials(nil, apiURL, credential, spaceID, "TerraformProvider")
}

func getApiCredential(c *Config, ctx context.Context) (client.ICredential, error) {
tflog.Debug(ctx, "GetClient: Trying the following auth methods in order of priority - APIKey, AccessToken")

if c.ApiKey != "" {
tflog.Debug(ctx, "GetClient: Attempting to authenticate with API Key")
credential, err := client.NewApiKey(c.ApiKey)
if err != nil {
return nil, err
}

return credential, nil
} else {
tflog.Debug(ctx, "GetClient: No API Key found")
}

if c.AccessToken != "" {
tflog.Debug(ctx, "GetClient: Attempting to authenticate with Access Token")
credential, err := client.NewAccessToken(c.AccessToken)
if err != nil {
return nil, err
}

return credential, nil
} else {
tflog.Debug(ctx, "GetClient: No Access Token found")
}

return nil, fmt.Errorf("either an APIKey or an AccessToken is required to connect to the Octopus Server instance")
}

func DataSourceConfiguration(req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) *Config {
if req.ProviderData == nil {
return nil
Expand Down
17 changes: 14 additions & 3 deletions octopusdeploy_framework/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import (
)

type octopusDeployFrameworkProvider struct {
Address types.String `tfsdk:"address"`
ApiKey types.String `tfsdk:"api_key"`
SpaceID types.String `tfsdk:"space_id"`
Address types.String `tfsdk:"address"`
ApiKey types.String `tfsdk:"api_key"`
AccessToken types.String `tfsdk:"access_token"`
SpaceID types.String `tfsdk:"space_id"`
}

var _ provider.Provider = (*octopusDeployFrameworkProvider)(nil)
Expand Down Expand Up @@ -45,6 +46,12 @@ func (p *octopusDeployFrameworkProvider) Configure(ctx context.Context, req prov
if config.ApiKey == "" {
config.ApiKey = os.Getenv("OCTOPUS_APIKEY")
}
if config.ApiKey == "" {
config.ApiKey = os.Getenv("OCTOPUS_API_KEY")
}
if config.AccessToken == "" {
config.AccessToken = os.Getenv("OCTOPUS_ACCESS_TOKEN")
}
config.Address = providerData.Address.ValueString()
if config.Address == "" {
config.Address = os.Getenv("OCTOPUS_URL")
Expand Down Expand Up @@ -128,6 +135,10 @@ func (p *octopusDeployFrameworkProvider) Schema(_ context.Context, req provider.
Optional: true,
Description: "The API key to use with the Octopus REST API",
},
"access_token": schema.StringAttribute{
Optional: true,
Description: "The OIDC Access Token to use with the Octopus REST API",
},
"space_id": schema.StringAttribute{
Optional: true,
Description: "The space ID to target",
Expand Down
44 changes: 41 additions & 3 deletions templates/guides/2-provider-configuration.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ subcategory: "Guides"

## Example usage

### API Key

`main.tf`

```hcl
Expand All @@ -25,13 +27,49 @@ provider "octopusdeploy" {
}
```

### Access Token (via Environment Variable)
OIDC Access Tokens are short-lived and typically generated per-run of an automated pipeline, such as GitHub Actions.
If you use the Access Token approach, we recommend sourcing the token from environment variable.

The environment variable fallback values that the Terraform Provider search for correspond to the values that pipeline steps like our [GitHub Login action](https://github.com/OctopusDeploy/login?tab=readme-ov-file#outputs) set in the pipeline context, so the provider will automatically pick up the value from environment variable.

`main.tf`

```hcl
terraform {
required_providers {
octopusdeploy = {
source = OctopusDeployLabs/octopusdeploy
}
}
}

provider "octopusdeploy" {
space_id = "..."
}
```

## Schema

### Required
* `address` (String) The Octopus Deploy server URL. This can also be set using the `OCTOPUS_URL` environment variable.
* `api_key` (String) The Octopus Deploy server API key. This can also be set using the `OCTOPUS_APIKEY` environment variable.
* `address` (String) The Octopus Deploy server URL.

and one of either
* `api_key` (String) The Octopus Deploy server API key.

OR
* `access_token` (String) The OIDC Access Token from an OIDC exchange.

### Optional
* `space_id` (String) The ID of the space to create the resources in.

**If `space_id` is not specified the default space will be used.**
**If `space_id` is not specified the default space will be used.**

### Environment Variable fallback
The following priority order will be used to calculate the final value for these configuration items:

| Configuration Item | Priority Order |
|--------------------|--------------------------------------------------------------------------------------------------|
| `address` | 1. Provider Configuration Block <br /> 2. env: `OCTOPUS_URL` |
| `api_key` | 1. Provider Configuration Block <br /> 2. env: `OCTOPUS_APIKEY` <br /> 3. env: `OCTOPUS_API_KEY` |
| `access_token` | 1. Provider Configuration Block <br /> 2. env: `OCTOPUS_ACCESS_TOKEN` |
Loading

0 comments on commit cce0c64

Please sign in to comment.