Skip to content

Commit

Permalink
Merge branch 'main' into waypoint/dont-make-details-resource-required
Browse files Browse the repository at this point in the history
  • Loading branch information
briancain authored Aug 21, 2024
2 parents 7b01220 + 772433a commit ce39700
Show file tree
Hide file tree
Showing 14 changed files with 417 additions and 47 deletions.
4 changes: 4 additions & 0 deletions .changelog/1053.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
```release-note:feature
Allow authenticating the provider using Workload Identity Federation via a
direct token in the provider configuration.
```
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
## v0.95.0 (August 21, 2024)
BREAKING CHANGES:

* waypoint: Remove version number from templates and add-on definition resources and data sources. [[GH-869](https://github.com/hashicorp/terraform-provider-hcp/pull/869)]

FEATURES:

* Allow authenticating the provider using Workload Identity Federation via a
direct token in the provider configuration. [[GH-1053](https://github.com/hashicorp/terraform-provider-hcp/pull/1053)]
* HCP Waypoint Application resource and data source can now read any output values associated with that application [[GH-871](https://github.com/hashicorp/terraform-provider-hcp/pull/871)]

IMPROVEMENTS:

* Updated documentation to communicate the APIs the provider leverages during usage. [[GH-1066](https://github.com/hashicorp/terraform-provider-hcp/pull/1066)]
* waypoint: The `readme_markdown_template` attribute for both template and add-on definition resources now accepts unencoded strings as well as base64 encoded strings. [[GH-894](https://github.com/hashicorp/terraform-provider-hcp/pull/894)]
## v0.94.1 (July 08, 2024)

BUG FIXES:
Expand Down
8 changes: 6 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ terraform {
required_providers {
hcp = {
source = "hashicorp/hcp"
version = "~> 0.94.1"
version = "~> 0.95.0"
}
}
}
Expand Down Expand Up @@ -134,7 +134,11 @@ resource "hcp_vault_cluster" "example" {
Required:

- `resource_name` (String) The resource_name of the Workload Identity Provider to exchange the token with.
- `token_file` (String) The path to a file containing a JWT token retrieved from an OpenID Connect (OIDC) or OAuth2 provider.

Optional:

- `token` (String) The JWT token retrieved from an OpenID Connect (OIDC) or OAuth2 provider. At least one of `token_file` or `token` must be set, if both are set then `token` takes precedence.
- `token_file` (String) The path to a file containing a JWT token retrieved from an OpenID Connect (OIDC) or OAuth2 provider. At least one of `token_file` or `token` must be set, if both are set then `token` takes precedence.
-> **Note:** See the [authentication guide](guides/auth.md) about a use case when specifying `project_id` is needed.

## API
Expand Down
2 changes: 1 addition & 1 deletion examples/provider/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ terraform {
required_providers {
hcp = {
source = "hashicorp/hcp"
version = "~> 0.94.1"
version = "~> 0.95.0"
}
}
}
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ require (
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hcp-sdk-go v0.100.0
github.com/hashicorp/hcp-sdk-go v0.105.0
github.com/hashicorp/terraform-plugin-docs v0.19.4
github.com/hashicorp/terraform-plugin-framework v1.5.0
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
Expand Down Expand Up @@ -60,7 +60,7 @@ require (
github.com/go-openapi/validate v0.24.0 // indirect
github.com/go-test/deep v1.1.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/go-cmp v0.6.0
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-checkpoint v0.5.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC16
github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA=
github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI=
github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE=
github.com/hashicorp/hcp-sdk-go v0.100.0 h1:nesBUlo0JunZwipk9uUqpVc5kXGwp4Hdrq2YbxeMSp0=
github.com/hashicorp/hcp-sdk-go v0.100.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/hcp-sdk-go v0.105.0 h1:KKqOBi13+wMEvMEG65brBJIXzvZcxjehVzk6vipaaSE=
github.com/hashicorp/hcp-sdk-go v0.105.0/go.mod h1:vQ4fzdL1AmhIAbCw+4zmFe5Hbpajj3NvRWkJoVuxmAk=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ=
Expand Down
81 changes: 64 additions & 17 deletions internal/clients/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"log"
"strings"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

"github.com/hashicorp/hcp-sdk-go/auth"
"github.com/hashicorp/hcp-sdk-go/auth/workload"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"

cloud_billing "github.com/hashicorp/hcp-sdk-go/clients/cloud-billing/preview/2020-11-05/client"
"github.com/hashicorp/hcp-sdk-go/clients/cloud-billing/preview/2020-11-05/client/billing_account_service"

Expand Down Expand Up @@ -97,10 +97,15 @@ type ClientConfig struct {

// WorkloadIdentityTokenFile and WorkloadIdentityResourceName can be set to
// indicate that authentication should occur by using workload identity
// federation. WorloadIdentityTokenFile indicates a file containing the
// token content and WorkloadIdentityResourceName is the workload identity
// provider resource name to authenticate against.
WorloadIdentityTokenFile string
// federation. WorkloadIdentityTokenFile indicates the token and
// WorkloadIdentityResourceName is the workload identity provider resource
// name to authenticate against.
//
// Alternatively, WorkloadIdentityToken can be set to the token directly. It
// is an error to set both WorkloadIdentityTokenFile and
// WorkloadIdentityToken.
WorkloadIdentityTokenFile string
WorkloadIdentityToken string
WorkloadIdentityResourceName string

// OrganizationID (optional) is the organization unique identifier to launch resources in.
Expand All @@ -122,17 +127,7 @@ func NewClient(config ClientConfig) (*Client, error) {
opts = append(opts, hcpConfig.WithClientCredentials(config.ClientID, config.ClientSecret))
} else if config.CredentialFile != "" {
opts = append(opts, hcpConfig.WithCredentialFilePath(config.CredentialFile))
} else if config.WorloadIdentityTokenFile != "" && config.WorkloadIdentityResourceName != "" {
// Build a credential file that points at the passed token file
cf := &auth.CredentialFile{
Scheme: auth.CredentialFileSchemeWorkload,
Workload: &workload.IdentityProviderConfig{
ProviderResourceName: config.WorkloadIdentityResourceName,
File: &workload.FileCredentialSource{
Path: config.WorloadIdentityTokenFile,
},
},
}
} else if cf := loadCredentialFile(config); cf != nil {
opts = append(opts, hcpConfig.WithCredentialFile(cf))
}

Expand Down Expand Up @@ -186,6 +181,58 @@ func NewClient(config ClientConfig) (*Client, error) {
return client, nil
}

// loadCredentialFile loads the credential file from the given config. If the
// config does not specify workload identity authentication, this function
// returns nil.
func loadCredentialFile(config ClientConfig) *auth.CredentialFile {
// hasWorkloadIdentityToken is true if the config specified a direct token.
hasWorkloadIdentityToken := config.WorkloadIdentityToken != ""
// hasWorkloadIdentityTokenFile is true if the config specified a path to a
// file that contains the token.
hasWorkloadIdentityTokenFile := config.WorkloadIdentityTokenFile != ""
// hasWorkloadIdentityResource is true if the config specified a resource
// name to authenticate against.
hasWorkloadIdentityResource := config.WorkloadIdentityResourceName != ""

// Overall, we consider workload identity authentication to be enabled if we
// have a token from either source (direct or within a file) and a resource
// name.
hasWorkloadIdentity := (hasWorkloadIdentityToken || hasWorkloadIdentityTokenFile) && hasWorkloadIdentityResource

if !hasWorkloadIdentity {
return nil
}

switch {
case hasWorkloadIdentityToken:
// The direct token takes priority over the file path in case both
// are set, so we'll check that first.
return &auth.CredentialFile{
Scheme: auth.CredentialFileSchemeWorkload,
Workload: &workload.IdentityProviderConfig{
ProviderResourceName: config.WorkloadIdentityResourceName,
Token: &workload.CredentialTokenSource{
Token: config.WorkloadIdentityToken,
},
},
}
default:
// If we don't have the token directly, fall back to checking the
// file. We checked earlier that at least one of the two options
// were set, so if the token wasn't set the file information must
// be present.
return &auth.CredentialFile{
Scheme: auth.CredentialFileSchemeWorkload,
Workload: &workload.IdentityProviderConfig{
ProviderResourceName: config.WorkloadIdentityResourceName,
File: &workload.FileCredentialSource{
Path: config.WorkloadIdentityTokenFile,
},
},
}
}
}

type providerMeta struct {
ModuleName string `cty:"module_name"`
}
Expand Down
81 changes: 81 additions & 0 deletions internal/clients/client_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package clients

import (
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcp-sdk-go/auth"
"github.com/hashicorp/hcp-sdk-go/auth/workload"
)

func Test_loadCredentialFile(t *testing.T) {
tcs := map[string]struct {
config ClientConfig
want *auth.CredentialFile
}{
"empty config": {
config: ClientConfig{},
},
"ignores with only resource": {
config: ClientConfig{
WorkloadIdentityResourceName: "my_resource",
},
},
"loads with file": {
config: ClientConfig{
WorkloadIdentityResourceName: "my_resource",
WorkloadIdentityTokenFile: "/path/to/token/file",
},
want: &auth.CredentialFile{
Scheme: auth.CredentialFileSchemeWorkload,
Workload: &workload.IdentityProviderConfig{
ProviderResourceName: "my_resource",
File: &workload.FileCredentialSource{
Path: "/path/to/token/file",
},
},
},
},
"loads with token": {
config: ClientConfig{
WorkloadIdentityResourceName: "my_resource",
WorkloadIdentityToken: "my_token",
},
want: &auth.CredentialFile{
Scheme: auth.CredentialFileSchemeWorkload,
Workload: &workload.IdentityProviderConfig{
ProviderResourceName: "my_resource",
Token: &workload.CredentialTokenSource{
Token: "my_token",
},
},
},
},
"defaults to token": {
config: ClientConfig{
WorkloadIdentityResourceName: "my_resource",
WorkloadIdentityTokenFile: "/path/to/token/file",
WorkloadIdentityToken: "my_token",
},
want: &auth.CredentialFile{
Scheme: auth.CredentialFileSchemeWorkload,
Workload: &workload.IdentityProviderConfig{
ProviderResourceName: "my_resource",
Token: &workload.CredentialTokenSource{
Token: "my_token",
},
},
},
},
}

for name, tc := range tcs {
t.Run(name, func(t *testing.T) {
got := loadCredentialFile(tc.config)
if cmp.Diff(tc.want, got) != "" {
t.Errorf("mismatch (-want +got): %s", cmp.Diff(tc.want, got))
}
})
}

}
14 changes: 7 additions & 7 deletions internal/clients/vault_secrets_preview.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,15 +101,15 @@ func GetRotatingSecretState(ctx context.Context, client *Client, loc *sharedmode

// CreateMongoDBAtlasRotationIntegration NOTE: currently just needed for tests
func CreateMongoDBAtlasRotationIntegration(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, integrationName, mongodbAtlasPublicKey, mongodbAtlasPrivateKey string) (*secretmodels.Secrets20231128MongoDBAtlasIntegration, error) {
body := secret_service.CreateMongoDBAtlasIntegrationBody{
body := secretmodels.SecretServiceCreateMongoDBAtlasIntegrationBody{
IntegrationName: integrationName,
MongodbAPIPublicKey: mongodbAtlasPublicKey,
MongodbAPIPrivateKey: mongodbAtlasPrivateKey,
}
params := secret_service.NewCreateMongoDBAtlasIntegrationParamsWithContext(ctx).
WithOrganizationID(loc.OrganizationID).
WithProjectID(loc.ProjectID).
WithBody(body)
WithBody(&body)

resp, err := client.VaultSecretsPreview.CreateMongoDBAtlasIntegration(params, nil)
if err != nil {
Expand Down Expand Up @@ -140,7 +140,7 @@ func CreateMongoDBAtlasRotatingSecret(
client *Client,
loc *sharedmodels.HashicorpCloudLocationLocation,
appName string,
requestBody secret_service.CreateMongoDBAtlasRotatingSecretBody,
requestBody *secretmodels.SecretServiceCreateMongoDBAtlasRotatingSecretBody,
) (*secretmodels.Secrets20231128CreateMongoDBAtlasRotatingSecretResponse, error) {
params := secret_service.NewCreateMongoDBAtlasRotatingSecretParamsWithContext(ctx).
WithOrganizationID(loc.OrganizationID).
Expand All @@ -158,7 +158,7 @@ func CreateMongoDBAtlasRotatingSecret(

// CreateAwsIntegration NOTE: currently just needed for tests
func CreateAwsIntegration(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, name, roleArn string) (*secretmodels.Secrets20231128AwsIntegration, error) {
body := secret_service.CreateAwsIntegrationBody{
body := secretmodels.SecretServiceCreateAwsIntegrationBody{
Name: name,
FederatedWorkloadIdentity: &secretmodels.Secrets20231128AwsFederatedWorkloadIdentityRequest{
Audience: loc.OrganizationID,
Expand All @@ -168,7 +168,7 @@ func CreateAwsIntegration(ctx context.Context, client *Client, loc *sharedmodels
params := secret_service.NewCreateAwsIntegrationParamsWithContext(ctx).
WithOrganizationID(loc.OrganizationID).
WithProjectID(loc.ProjectID).
WithBody(body)
WithBody(&body)

resp, err := client.VaultSecretsPreview.CreateAwsIntegration(params, nil)
if err != nil {
Expand All @@ -195,7 +195,7 @@ func DeleteAwsIntegration(ctx context.Context, client *Client, loc *sharedmodels

// CreateAwsDynamicSecret NOTE: currently just needed for tests
func CreateAwsDynamicSecret(ctx context.Context, client *Client, loc *sharedmodels.HashicorpCloudLocationLocation, appName, integrationName, name, roleArn string) (*secretmodels.Secrets20231128AwsDynamicSecret, error) {
body := secret_service.CreateAwsDynamicSecretBody{
body := secretmodels.SecretServiceCreateAwsDynamicSecretBody{
AssumeRole: &secretmodels.Secrets20231128AssumeRoleRequest{RoleArn: roleArn},
DefaultTTL: "3600s",
IntegrationName: integrationName,
Expand All @@ -205,7 +205,7 @@ func CreateAwsDynamicSecret(ctx context.Context, client *Client, loc *sharedmode
WithOrganizationID(loc.OrganizationID).
WithProjectID(loc.ProjectID).
WithAppName(appName).
WithBody(body)
WithBody(&body)

resp, err := client.VaultSecretsPreview.CreateAwsDynamicSecret(params, nil)
if err != nil {
Expand Down
Loading

0 comments on commit ce39700

Please sign in to comment.