Skip to content

Commit cb318c9

Browse files
helenjwiuri-slywitch-hashicorpJarrettSpikerdominic-retli-hashisebasslash
authored
Hold Your Own Key Support (#1201)
* Add support for HYOK Configurations and OIDC Configurations (#1162) Co-authored-by: Helen Jiang <[email protected]> * Add support for Customer Key Version and Encrypted Data Keys (#1203) Co-authored-by: Jarrett Spiker <[email protected]> * [TF-27661] Add support for HYOK related attributes (#1192) * initial attribute changes, wip * Add support for HYOK Configurations and OIDC Configurations (#1162) Co-authored-by: Helen Jiang <[email protected]> * Update workspace.go Co-authored-by: Jarrett Spiker <[email protected]> * Add support for HYOK Configurations and OIDC Configurations (#1162) Co-authored-by: Helen Jiang <[email protected]> * Add support for Customer Key Version and Encrypted Data Keys (#1203) Co-authored-by: Jarrett Spiker <[email protected]> * Updating attributes. * Add support for HYOK Configurations and OIDC Configurations (#1162) Co-authored-by: Helen Jiang <[email protected]> * Add support for Customer Key Version and Encrypted Data Keys (#1203) Co-authored-by: Jarrett Spiker <[email protected]> * Updating agent_pool. Added test case. * Updated agent pool integration test file. * Revert commented section. * Updating organization. WIP organization_integration_test. * Updated organization integration test. * Updating attributes. Updating test cases. * Added workspace integration test cases * Updated test cases. * Updated state_version. Updated Read test cases. * Updated hyok tests. Added environment variables. * Updated errors.go * WIP StateVersion * Updated skipHYOKIntegrationTests if-statement. * Added hyok-testing.sh to scripts folder. Finished state_version testing and new functions. * Updated uploading test. * Added comments to UploadSanitizedState. * Updated hyok test cases. * Updating state_version_mocks.go. --------- Co-authored-by: Helen Jiang <[email protected]> Co-authored-by: Jarrett Spiker <[email protected]> Co-authored-by: Helen Jiang <[email protected]> * Update CHANGELOG.md * Remove UpdatedAt and RevokedAt fields from hyok_customer_key_version * added workplaces secured * renamed workplaces to workspaces... oops * Update agent_pool.go Co-authored-by: Sebastian Rivera <[email protected]> * Moved valid function in aws_oidc_configuration.go * Removing "omitempty" from HYOKEncryptedDataKey relationships * Changed from "string" to "*string" in state_version.go and added url.PathEscape in request calls for hyok stuff. * Moving HYOK organization validation to helper_test.go * Updating mock file. --------- Co-authored-by: iuri-slywitch-hashicorp <[email protected]> Co-authored-by: Jarrett Spiker <[email protected]> Co-authored-by: Dominic Retli <[email protected]> Co-authored-by: Sebastian Rivera <[email protected]> Co-authored-by: Iuri Slywitch <[email protected]>
1 parent 130528f commit cb318c9

31 files changed

+3448
-23
lines changed

.github/actions/test-go-tfe/action.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,14 @@ runs:
9797
GITHUB_REGISTRY_MODULE_IDENTIFIER: "hashicorp/terraform-random-module"
9898
GITHUB_REGISTRY_NO_CODE_MODULE_IDENTIFIER: "hashicorp/terraform-random-no-code-module"
9999
OAUTH_CLIENT_GITHUB_TOKEN: "${{ inputs.oauth-client-github-token }}"
100+
SKIP_HYOK_INTEGRATION_TESTS: "${{ inputs.skip-hyok-integration-tests }}"
101+
HYOK_ORGANIZATION_NAME: "${{ inputs.hyok-organization-name }}"
102+
HYOK_WORKSPACE_NAME: "${{ inputs.hyok-workspace-name }}"
103+
HYOK_POOL_ID: "${{ inputs.hyok-pool-id }}"
104+
HYOK_PLAN_ID: "${{ inputs.hyok-plan-id }}"
105+
HYOK_STATE_VERSION_ID: "${{ inputs.hyok-state-version-id }}"
106+
HYOK_CUSTOMER_KEY_VERSION_ID: "${{ inputs.hyok-customer-key-version-id }}"
107+
HYOK_ENCRYPTED_DATA_KEY_ID: "${{ inputs.hyok-encrypted-data-key-id }}"
100108
GO111MODULE: "on"
101109
ENABLE_TFE: ${{ inputs.enterprise }}
102110
run: |

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
## Enhancements
44
* Exports the StackConfiguration UploadTarGzip receiver function [#1219](https://github.com/hashicorp/go-tfe/pull/1219)
55
* Updates BETA stacks resource schemas to match latest API spec by @ctrombley [#1220](https://github.com/hashicorp/go-tfe/pull/1220)
6+
* Adds support for Hold Your Own Key by @helenjw , @iuri-slywitch-hashicorp and @dominic-retli-hashi [#1201](https://github.com/hashicorp/go-tfe/pull/1201)
67

78
## Deprecations
89

agent_pool.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -66,18 +66,22 @@ type AgentPool struct {
6666
CreatedAt time.Time `jsonapi:"attr,created-at,iso8601"`
6767

6868
// Relations
69-
Organization *Organization `jsonapi:"relation,organization"`
70-
Workspaces []*Workspace `jsonapi:"relation,workspaces"`
71-
AllowedWorkspaces []*Workspace `jsonapi:"relation,allowed-workspaces"`
72-
AllowedProjects []*Project `jsonapi:"relation,allowed-projects"`
73-
ExcludedWorkspaces []*Workspace `jsonapi:"relation,excluded-workspaces"`
69+
Organization *Organization `jsonapi:"relation,organization"`
70+
HYOKConfigurations []*HYOKConfiguration `jsonapi:"relation,hyok-configurations"`
71+
Workspaces []*Workspace `jsonapi:"relation,workspaces"`
72+
AllowedWorkspaces []*Workspace `jsonapi:"relation,allowed-workspaces"`
73+
AllowedProjects []*Project `jsonapi:"relation,allowed-projects"`
74+
ExcludedWorkspaces []*Workspace `jsonapi:"relation,excluded-workspaces"`
7475
}
7576

7677
// A list of relations to include
7778
// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/agents#available-related-resources
7879
type AgentPoolIncludeOpt string
7980

80-
const AgentPoolWorkspaces AgentPoolIncludeOpt = "workspaces"
81+
const (
82+
AgentPoolWorkspaces AgentPoolIncludeOpt = "workspaces"
83+
AgentPoolHYOKConfigurations AgentPoolIncludeOpt = "hyok-configurations"
84+
)
8185

8286
type AgentPoolReadOptions struct {
8387
Include []AgentPoolIncludeOpt `url:"include,omitempty"`
@@ -188,7 +192,7 @@ func (s *agentPools) ReadWithOptions(ctx context.Context, agentpoolID string, op
188192
}
189193

190194
u := fmt.Sprintf("agent-pools/%s", url.PathEscape(agentpoolID))
191-
req, err := s.client.NewRequest("GET", u, nil)
195+
req, err := s.client.NewRequest("GET", u, &options)
192196
if err != nil {
193197
return nil, err
194198
}

agent_pool_integration_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package tfe
55

66
import (
77
"context"
8+
"os"
89
"testing"
910

1011
"github.com/stretchr/testify/assert"
@@ -343,6 +344,22 @@ func TestAgentPoolsRead(t *testing.T) {
343344
require.NoError(t, err)
344345
assert.NotEmpty(t, k.Workspaces[0])
345346
})
347+
348+
t.Run("read hyok configurations of an agent pool", func(t *testing.T) {
349+
skipHYOKIntegrationTests(t)
350+
351+
// replace the environment variable with a valid agent pool ID that has HYOK configurations
352+
hyokPoolID := os.Getenv("HYOK_POOL_ID")
353+
if hyokPoolID == "" {
354+
t.Fatal("Export a valid HYOK_POOL_ID before running this test!")
355+
}
356+
357+
k, err := client.AgentPools.ReadWithOptions(ctx, hyokPoolID, &AgentPoolReadOptions{
358+
Include: []AgentPoolIncludeOpt{AgentPoolHYOKConfigurations},
359+
})
360+
require.NoError(t, err)
361+
assert.NotEmpty(t, k.HYOKConfigurations)
362+
})
346363
}
347364

348365
func TestAgentPoolsReadCreatedAt(t *testing.T) {

aws_oidc_configuration.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package tfe
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
)
8+
9+
const OIDCConfigPathFormat = "oidc-configurations/%s"
10+
11+
// AWSOIDCConfigurations describes all the AWS OIDC configuration related methods that the HCP Terraform API supports.
12+
// HCP Terraform API docs:
13+
// https://developer.hashicorp.com/terraform/cloud-docs/api-docs/hold-your-own-key/oidc-configurations/aws
14+
type AWSOIDCConfigurations interface {
15+
Create(ctx context.Context, organization string, options AWSOIDCConfigurationCreateOptions) (*AWSOIDCConfiguration, error)
16+
17+
Read(ctx context.Context, oidcID string) (*AWSOIDCConfiguration, error)
18+
19+
Update(ctx context.Context, oidcID string, options AWSOIDCConfigurationUpdateOptions) (*AWSOIDCConfiguration, error)
20+
21+
Delete(ctx context.Context, oidcID string) error
22+
}
23+
24+
type awsOIDCConfigurations struct {
25+
client *Client
26+
}
27+
28+
var _ AWSOIDCConfigurations = &awsOIDCConfigurations{}
29+
30+
type AWSOIDCConfiguration struct {
31+
ID string `jsonapi:"primary,aws-oidc-configurations"`
32+
RoleARN string `jsonapi:"attr,role-arn"`
33+
34+
Organization *Organization `jsonapi:"relation,organization"`
35+
}
36+
37+
type AWSOIDCConfigurationCreateOptions struct {
38+
// Type is a public field utilized by JSON:API to
39+
// set the resource type via the field tag.
40+
// It is not a user-defined value and does not need to be set.
41+
// https://jsonapi.org/format/#crud-creating
42+
Type string `jsonapi:"primary,aws-oidc-configurations"`
43+
44+
// Attributes
45+
RoleARN string `jsonapi:"attr,role-arn"`
46+
}
47+
48+
type AWSOIDCConfigurationUpdateOptions struct {
49+
// Type is a public field utilized by JSON:API to
50+
// set the resource type via the field tag.
51+
// It is not a user-defined value and does not need to be set.
52+
// https://jsonapi.org/format/#crud-creating
53+
Type string `jsonapi:"primary,aws-oidc-configurations"`
54+
55+
// Attributes
56+
RoleARN string `jsonapi:"attr,role-arn"`
57+
}
58+
59+
func (o *AWSOIDCConfigurationCreateOptions) valid() error {
60+
if o.RoleARN == "" {
61+
return ErrRequiredRoleARN
62+
}
63+
64+
return nil
65+
}
66+
67+
func (o *AWSOIDCConfigurationUpdateOptions) valid() error {
68+
if o.RoleARN == "" {
69+
return ErrRequiredRoleARN
70+
}
71+
72+
return nil
73+
}
74+
75+
func (aoc *awsOIDCConfigurations) Create(ctx context.Context, organization string, options AWSOIDCConfigurationCreateOptions) (*AWSOIDCConfiguration, error) {
76+
if !validStringID(&organization) {
77+
return nil, ErrInvalidOrg
78+
}
79+
80+
if err := options.valid(); err != nil {
81+
return nil, err
82+
}
83+
84+
req, err := aoc.client.NewRequest("POST", fmt.Sprintf("organizations/%s/oidc-configurations", url.PathEscape(organization)), &options)
85+
if err != nil {
86+
return nil, err
87+
}
88+
89+
awsOIDCConfiguration := &AWSOIDCConfiguration{}
90+
err = req.Do(ctx, awsOIDCConfiguration)
91+
if err != nil {
92+
return nil, err
93+
}
94+
95+
return awsOIDCConfiguration, nil
96+
}
97+
98+
func (aoc *awsOIDCConfigurations) Read(ctx context.Context, oidcID string) (*AWSOIDCConfiguration, error) {
99+
req, err := aoc.client.NewRequest("GET", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)
100+
if err != nil {
101+
return nil, err
102+
}
103+
104+
awsOIDCConfiguration := &AWSOIDCConfiguration{}
105+
err = req.Do(ctx, awsOIDCConfiguration)
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
return awsOIDCConfiguration, nil
111+
}
112+
113+
func (aoc *awsOIDCConfigurations) Update(ctx context.Context, oidcID string, options AWSOIDCConfigurationUpdateOptions) (*AWSOIDCConfiguration, error) {
114+
if !validStringID(&oidcID) {
115+
return nil, ErrInvalidOIDC
116+
}
117+
118+
if err := options.valid(); err != nil {
119+
return nil, err
120+
}
121+
122+
req, err := aoc.client.NewRequest("PATCH", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), &options)
123+
if err != nil {
124+
return nil, err
125+
}
126+
127+
awsOIDCConfiguration := &AWSOIDCConfiguration{}
128+
err = req.Do(ctx, awsOIDCConfiguration)
129+
if err != nil {
130+
return nil, err
131+
}
132+
133+
return awsOIDCConfiguration, nil
134+
}
135+
136+
func (aoc *awsOIDCConfigurations) Delete(ctx context.Context, oidcID string) error {
137+
if !validStringID(&oidcID) {
138+
return ErrInvalidOIDC
139+
}
140+
141+
req, err := aoc.client.NewRequest("DELETE", fmt.Sprintf(OIDCConfigPathFormat, url.PathEscape(oidcID)), nil)
142+
if err != nil {
143+
return err
144+
}
145+
146+
return req.Do(ctx, nil)
147+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package tfe
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
// These tests are intended for local execution only, as OIDC configurations for HYOK requires specific conditions.
12+
// To run them locally, follow the instructions outlined in hyok_configuration_integration_test.go
13+
14+
func TestAWSOIDCConfigurationCreateDelete(t *testing.T) {
15+
skipHYOKIntegrationTests(t)
16+
17+
client := testClient(t)
18+
ctx := context.Background()
19+
20+
orgTest := testHyokOrganization(t, client)
21+
22+
t.Run("with valid options", func(t *testing.T) {
23+
opts := AWSOIDCConfigurationCreateOptions{
24+
RoleARN: "arn:aws:iam::123456789012:role/some-role",
25+
}
26+
27+
oidcConfig, err := client.AWSOIDCConfigurations.Create(ctx, orgTest.Name, opts)
28+
require.NoError(t, err)
29+
require.NotNil(t, oidcConfig)
30+
assert.Equal(t, oidcConfig.RoleARN, opts.RoleARN)
31+
32+
// delete the created configuration
33+
err = client.AWSOIDCConfigurations.Delete(ctx, oidcConfig.ID)
34+
require.NoError(t, err)
35+
})
36+
37+
t.Run("missing role ARN", func(t *testing.T) {
38+
opts := AWSOIDCConfigurationCreateOptions{}
39+
40+
_, err := client.AWSOIDCConfigurations.Create(ctx, orgTest.Name, opts)
41+
assert.ErrorIs(t, err, ErrRequiredRoleARN)
42+
})
43+
}
44+
45+
func TestAWSOIDCConfigurationRead(t *testing.T) {
46+
skipHYOKIntegrationTests(t)
47+
48+
client := testClient(t)
49+
ctx := context.Background()
50+
51+
orgTest := testHyokOrganization(t, client)
52+
53+
oidcConfig, oidcConfigCleanup := createAWSOIDCConfiguration(t, client, orgTest)
54+
t.Cleanup(oidcConfigCleanup)
55+
56+
t.Run("fetch existing configuration", func(t *testing.T) {
57+
fetched, err := client.AWSOIDCConfigurations.Read(ctx, oidcConfig.ID)
58+
require.NoError(t, err)
59+
require.NotEmpty(t, fetched)
60+
})
61+
62+
t.Run("fetching non-existing configuration", func(t *testing.T) {
63+
_, err := client.AWSOIDCConfigurations.Read(ctx, "awsoidc-notreal")
64+
assert.ErrorIs(t, err, ErrResourceNotFound)
65+
})
66+
}
67+
68+
func TestAWSOIDCConfigurationsUpdate(t *testing.T) {
69+
skipHYOKIntegrationTests(t)
70+
71+
client := testClient(t)
72+
ctx := context.Background()
73+
74+
orgTest := testHyokOrganization(t, client)
75+
76+
oidcConfig, oidcConfigCleanup := createAWSOIDCConfiguration(t, client, orgTest)
77+
t.Cleanup(oidcConfigCleanup)
78+
79+
t.Run("with valid options", func(t *testing.T) {
80+
opts := AWSOIDCConfigurationUpdateOptions{
81+
RoleARN: "arn:aws:iam::123456789012:role/some-role-2",
82+
}
83+
updated, err := client.AWSOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)
84+
require.NoError(t, err)
85+
require.NotEmpty(t, updated)
86+
assert.Equal(t, opts.RoleARN, updated.RoleARN)
87+
})
88+
89+
t.Run("missing role ARN", func(t *testing.T) {
90+
opts := AWSOIDCConfigurationUpdateOptions{}
91+
_, err := client.AWSOIDCConfigurations.Update(ctx, oidcConfig.ID, opts)
92+
assert.ErrorIs(t, err, ErrRequiredRoleARN)
93+
})
94+
}

0 commit comments

Comments
 (0)