Skip to content

Commit

Permalink
Add support for TFC workload identity token
Browse files Browse the repository at this point in the history
  • Loading branch information
alexhung committed Apr 11, 2024
1 parent a4b5c9e commit 10aab39
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 35 deletions.
80 changes: 71 additions & 9 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ curl -sL ${host}/artifactory/api/system/licenses/ | jq .
terraform {
required_providers {
artifactory = {
source = "registry.terraform.io/jfrog/artifactory"
version = "9.9.0"
source = "jfrog/artifactory"
version = "10.5.1"
}
platform = {
source = "registry.terraform.io/jfrog/platform"
version = "1.0.0"
source = "jfrog/platform"
version = "1.6.0"
}
}
}
Expand All @@ -55,6 +55,14 @@ provider "platform" {
// supply JFROG_ACCESS_TOKEN as env var
}
resource "platform_global_role" "my-global-role" {
name = "my-global-role"
description = "Test description"
type = "CUSTOM_GLOBAL"
environments = ["DEV"]
actions = ["READ_REPOSITORY", "READ_BUILD"]
}
resource "artifactory_local_generic_repository" "my-generic-local" {
key = "my-generic-local"
}
Expand Down Expand Up @@ -87,27 +95,81 @@ resource "platform_workers_service" "my-workers-service" {

## Authentication

The JFrog Platform provider supports one type of authentication using scoped token.
The JFrog Platform provider supports for the following types of authentication:
* Scoped token
* Terraform Cloud OIDC provider

### Scoped Token

Artifactory scoped tokens may be used via the Authorization header by providing the `access_token` field to the provider block. Getting this value from the environment is supported with the `JFROG_ACCESS_TOKEN` environment variable
JFrog scoped tokens may be used via the HTTP Authorization header by providing the `access_token` field to the provider block. Getting this value from the environment is supported with the `JFROG_ACCESS_TOKEN` environment variable.

Usage:
```hcl
# Configure the Artifactory provider
```terraform
provider "platform" {
url = "my.jfrog.io"
access_token = "abc...xy"
}
```

### Terraform Cloud OIDC Provider

If you are using this provider on Terraform Cloud and wish to use dynamic credentials instead of static access token for authentication with JFrog platform, you can leverage Terraform as the OIDC provider.

To setup dynamic credentials, follow these steps:
1. Configure Terraform Cloud as a generic OIDC provider
2. Set environment variable in your Terraform Workspace
3. Setup Terraform Cloud in your configuration

During the provider start up, if it finds env var `TFC_WORKLOAD_IDENTITY_TOKEN` it will use this token with your JFrog instance to exchange for a short-live access token. If that is successful, the provider will the access token for all subsequent API requests with the JFrog instance.

#### Configure Terraform Cloud as generic OIDC provider

Follow [confgure an OIDC integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration). Enter a name for the provider, e.g. `terraform-cloud`. Use `https://app.terraform.io` for "Provider URL". Choose your own value for "Audience", e.g. `jfrog-terraform-cloud`.

Then [configure an identity mapping](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-identity-mappings) with an empty "Claims JSON" (`{}`), and select the "Token scope", "User", and "Service" as desired.

#### Set environment variable in your Terraform Workspace

In your workspace, add an environment variable `TFC_WORKLOAD_IDENTITY_AUDIENCE` with audience value (e.g. `jfrog-terraform-cloud`) from JFrog OIDC integration above. See [Manually Generating Workload Identity Tokens](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/manual-generation) for more details.

When a run starts on Terraform Cloud, it will create a workload identity token with the specified audience and assigns it to the environment variable `TFC_WORKLOAD_IDENTITY_TOKEN` for the provider to consume.

#### Setup Terraform Cloud in your configuration

Add `cloud` block to `terraform` block, and add `oidc_provider_name` attribute (from JFrog OIDC integration) to provider block:

```terraform
terraform {
cloud {
organization = "my-org"
workspaces {
name = "my-workspace"
}
}
required_providers {
platform = {
source = "jfrog/platform"
version = "1.6.1"
}
}
}
provider "platform" {
url = "https://myinstance.jfrog.io"
oidc_provider_name = "terraform-cloud"
}
```

**Note:** Ensure `access_token` attribute is not set

<!-- schema generated by tfplugindocs -->
## Schema

### Optional

- `access_token` (String, Sensitive) This is a access token that can be given to you by your admin under `Platform Configuration -> User Management -> Access Tokens`. This can also be sourced from the `JFROG_ACCESS_TOKEN` environment variable.
- `check_license` (Boolean) Toggle for pre-flight checking of Artifactory Pro and Enterprise license. Default to `true`.
- `myjfrog_api_token` (String, Sensitive) MyJFrog API token that allows you to make changes to your JFrog account. See [Generate a Token in MyJFrog](https://jfrog.com/help/r/jfrog-hosting-models-documentation/generate-a-token-in-myjfrog) for more details. This can also be sourced from the `JFROG_MYJFROG_API_TOKEN` environment variable.
- `myjfrog_api_token` (String, Sensitive) MyJFrog API token that allows you to make changes to your JFrog account. See [Generate a Token in MyJFrog](https://jfrog.com/help/r/jfrog-hosting-models-documentation/generate-a-token-in-myjfrog) for more details. This can also be sourced from the `JFROG_MYJFROG_API_TOKEN` environment variable.
- `oidc_provider_name` (String) OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details.
- `url` (String) JFrog Platform URL. This can also be sourced from the `JFROG_URL` environment variable.
16 changes: 12 additions & 4 deletions examples/example.tf
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
terraform {
required_providers {
artifactory = {
source = "registry.terraform.io/jfrog/artifactory"
version = "9.9.0"
source = "jfrog/artifactory"
version = "10.5.1"
}
platform = {
source = "registry.terraform.io/jfrog/platform"
version = "1.0.0"
source = "jfrog/platform"
version = "1.6.0"
}
}
}
Expand All @@ -26,6 +26,14 @@ provider "platform" {
// supply JFROG_ACCESS_TOKEN as env var
}

resource "platform_global_role" "my-global-role" {
name = "my-global-role"
description = "Test description"
type = "CUSTOM_GLOBAL"
environments = ["DEV"]
actions = ["READ_REPOSITORY", "READ_BUILD"]
}

resource "artifactory_local_generic_repository" "my-generic-local" {
key = "my-generic-local"
}
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require (
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
github.com/hashicorp/terraform-plugin-go v0.22.1
github.com/hashicorp/terraform-plugin-testing v1.7.0
github.com/jfrog/terraform-provider-shared v1.22.4
github.com/jfrog/terraform-provider-shared v1.23.0
github.com/samber/lo v1.39.0
)

Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,8 @@ github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM=
github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
github.com/jfrog/terraform-provider-shared v1.22.4 h1:WjJKEzFPKgfnQVg0GXE+6x64sSqDWXKkZeHEKW/x+sk=
github.com/jfrog/terraform-provider-shared v1.22.4/go.mod h1:OozwvfahZU4Q9u3kXdpQI3h/Etrs2DXoouQDrpxB1cQ=
github.com/jfrog/terraform-provider-shared v1.23.0 h1:O8yKQMFrinHwt9CUxZag6TwiXMlajIFO66CAxS6VKqk=
github.com/jfrog/terraform-provider-shared v1.23.0/go.mod h1:OozwvfahZU4Q9u3kXdpQI3h/Etrs2DXoouQDrpxB1cQ=
github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c=
github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
Expand Down
52 changes: 37 additions & 15 deletions pkg/platform/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,11 @@ type PlatformProvider struct {
}

type platformProviderModel struct {
Url types.String `tfsdk:"url"`
AccessToken types.String `tfsdk:"access_token"`
MyJFrogAPIToken types.String `tfsdk:"myjfrog_api_token"`
CheckLicense types.Bool `tfsdk:"check_license"`
Url types.String `tfsdk:"url"`
AccessToken types.String `tfsdk:"access_token"`
MyJFrogAPIToken types.String `tfsdk:"myjfrog_api_token"`
OIDCProviderName types.String `tfsdk:"oidc_provider_name"`
CheckLicense types.Bool `tfsdk:"check_license"`
}

func NewProvider() func() provider.Provider {
Expand All @@ -60,16 +61,38 @@ func (p *PlatformProvider) Configure(ctx context.Context, req provider.Configure
return
}

// Check configuration data, which should take precedence over
platformClient, err := client.Build(url, productId)
if err != nil {
resp.Diagnostics.AddError(
"Error creating Resty client",
err.Error(),
)
}

oidcAccessToken, err := util.OIDCTokenExchange(ctx, platformClient, config.OIDCProviderName.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Failed OIDC ID token exchange",
err.Error(),
)
}

// use token from OIDC provider, which should take precedence over
// environment variable data, if found.
if oidcAccessToken != "" {
accessToken = oidcAccessToken
}

// use token from configuration, which should take precedence over
// environment variable data or OIDC provider, if found.
if config.AccessToken.ValueString() != "" {
accessToken = config.AccessToken.ValueString()
}

if accessToken == "" {
resp.Diagnostics.AddError(
"Missing JFrog Access Token",
"While configuring the provider, the Access Token was not found in the JFROG_ACCESS_TOKEN environment variable or provider configuration block access_token attribute.",
"While configuring the provider, the Access Token was not found in the JFROG_ACCESS_TOKEN environment variable, provider configuration block access_token attribute, or Terraform Cloud TFC_WORKLOAD_IDENTITY_TOKEN environment variable.",
)
return
}
Expand Down Expand Up @@ -111,14 +134,6 @@ func (p *PlatformProvider) Configure(ctx context.Context, req provider.Configure
myJFrogClient = c
}

platformClient, err := client.Build(url, productId)
if err != nil {
resp.Diagnostics.AddError(
"Error creating Resty client",
err.Error(),
)
}

platformClient, err = client.AddAuth(platformClient, "", accessToken)
if err != nil {
resp.Diagnostics.AddError(
Expand Down Expand Up @@ -211,7 +226,14 @@ func (p *PlatformProvider) Schema(ctx context.Context, req provider.SchemaReques
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
MarkdownDescription: "MyJFrog API token that allows you to make changes to your JFrog account. See [Generate a Token in MyJFrog](https://jfrog.com/help/r/jfrog-hosting-models-documentation/generate-a-token-in-myjfrog) for more details. This can also be sourced from the `JFROG_MYJFROG_API_TOKEN` environment variable.",
MarkdownDescription: "MyJFrog API token that allows you to make changes to your JFrog account. See [Generate a Token in MyJFrog](https://jfrog.com/help/r/jfrog-hosting-models-documentation/generate-a-token-in-myjfrog) for more details. This can also be sourced from the `JFROG_MYJFROG_API_TOKEN` environment variable.",
},
"oidc_provider_name": schema.StringAttribute{
Optional: true,
Validators: []validator.String{
stringvalidator.LengthAtLeast(1),
},
MarkdownDescription: "OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details.",
},
"check_license": schema.BoolAttribute{
Optional: true,
Expand Down
61 changes: 57 additions & 4 deletions templates/index.md.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,72 @@ curl -sL ${host}/artifactory/api/system/licenses/ | jq .

## Authentication

The JFrog Platform provider supports one type of authentication using scoped token.
The JFrog Platform provider supports for the following types of authentication:
* Scoped token
* Terraform Cloud OIDC provider

### Scoped Token

Artifactory scoped tokens may be used via the Authorization header by providing the `access_token` field to the provider block. Getting this value from the environment is supported with the `JFROG_ACCESS_TOKEN` environment variable
JFrog scoped tokens may be used via the HTTP Authorization header by providing the `access_token` field to the provider block. Getting this value from the environment is supported with the `JFROG_ACCESS_TOKEN` environment variable.

Usage:
```hcl
# Configure the Artifactory provider
```terraform
provider "platform" {
url = "my.jfrog.io"
access_token = "abc...xy"
}
```

### Terraform Cloud OIDC Provider

If you are using this provider on Terraform Cloud and wish to use dynamic credentials instead of static access token for authentication with JFrog platform, you can leverage Terraform as the OIDC provider.

To setup dynamic credentials, follow these steps:
1. Configure Terraform Cloud as a generic OIDC provider
2. Set environment variable in your Terraform Workspace
3. Setup Terraform Cloud in your configuration

During the provider start up, if it finds env var `TFC_WORKLOAD_IDENTITY_TOKEN` it will use this token with your JFrog instance to exchange for a short-live access token. If that is successful, the provider will the access token for all subsequent API requests with the JFrog instance.

#### Configure Terraform Cloud as generic OIDC provider

Follow [confgure an OIDC integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration). Enter a name for the provider, e.g. `terraform-cloud`. Use `https://app.terraform.io` for "Provider URL". Choose your own value for "Audience", e.g. `jfrog-terraform-cloud`.

Then [configure an identity mapping](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-identity-mappings) with an empty "Claims JSON" (`{}`), and select the "Token scope", "User", and "Service" as desired.

#### Set environment variable in your Terraform Workspace

In your workspace, add an environment variable `TFC_WORKLOAD_IDENTITY_AUDIENCE` with audience value (e.g. `jfrog-terraform-cloud`) from JFrog OIDC integration above. See [Manually Generating Workload Identity Tokens](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/manual-generation) for more details.

When a run starts on Terraform Cloud, it will create a workload identity token with the specified audience and assigns it to the environment variable `TFC_WORKLOAD_IDENTITY_TOKEN` for the provider to consume.

#### Setup Terraform Cloud in your configuration

Add `cloud` block to `terraform` block, and add `oidc_provider_name` attribute (from JFrog OIDC integration) to provider block:

```terraform
terraform {
cloud {
organization = "my-org"
workspaces {
name = "my-workspace"
}
}

required_providers {
platform = {
source = "jfrog/platform"
version = "1.6.1"
}
}
}

provider "platform" {
url = "https://myinstance.jfrog.io"
oidc_provider_name = "terraform-cloud"
}
```

**Note:** Ensure `access_token` attribute is not set

{{ .SchemaMarkdown | trimspace }}

0 comments on commit 10aab39

Please sign in to comment.