Skip to content

Commit 6a519b5

Browse files
committed
Add support for rotate / trim admin OIDC endpoints
1 parent eff1681 commit 6a519b5

File tree

4 files changed

+182
-0
lines changed

4 files changed

+182
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
## Enhancements
44
* Adds `ManageMembership` permission to team `OrganizationAccess` by @JarrettSpiker [#652](https://github.com/hashicorp/go-tfe/pull/652)
5+
* Adds `RotateKey` and `TrimKey` Admin endpoints by @mpminardi [#666](https://github.com/hashicorp/go-tfe/pull/666)
56

67
# v1.20.0
78

admin_setting.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type AdminSettings struct {
1414
SMTP SMTPSettings
1515
Twilio TwilioSettings
1616
Customization CustomizationSettings
17+
OIDC OIDCSettings
1718
}
1819

1920
func newAdminSettings(client *Client) *AdminSettings {
@@ -24,5 +25,6 @@ func newAdminSettings(client *Client) *AdminSettings {
2425
SMTP: &adminSMTPSettings{client: client},
2526
Twilio: &adminTwilioSettings{client: client},
2627
Customization: &adminCustomizationSettings{client: client},
28+
OIDC: &adminOIDCSettings{client: client},
2729
}
2830
}

admin_setting_oidc.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package tfe
5+
6+
import (
7+
"context"
8+
)
9+
10+
// Compile-time proof of interface implementation.
11+
var _ OIDCSettings = (*adminOIDCSettings)(nil)
12+
13+
// OidcSettings describes all the OIDC admin settings for the Admin Setting API.
14+
// https://developer.hashicorp.com/terraform/enterprise/api-docs/admin/settings
15+
type OIDCSettings interface {
16+
// Rotate the key used for signing OIDC tokens for workload identity
17+
RotateKey(ctx context.Context) error
18+
19+
// Trim old version of the key used for signing OIDC tokens for workload identity
20+
TrimKey(ctx context.Context) error
21+
}
22+
23+
type adminOIDCSettings struct {
24+
client *Client
25+
}
26+
27+
// Rotate the key used for signing OIDC tokens for workload identity
28+
func (a *adminOIDCSettings) RotateKey(ctx context.Context) error {
29+
req, err := a.client.NewRequest("POST", "admin/oidc-settings/actions/rotate-key", nil)
30+
if err != nil {
31+
return err
32+
}
33+
34+
return req.Do(ctx, nil)
35+
}
36+
37+
// Trim old version of the key used for signing OIDC tokens for workload identity
38+
func (a *adminOIDCSettings) TrimKey(ctx context.Context) error {
39+
req, err := a.client.NewRequest("POST", "admin/oidc-settings/actions/trim-key", nil)
40+
if err != nil {
41+
return err
42+
}
43+
44+
return req.Do(ctx, nil)
45+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package tfe
5+
6+
import (
7+
"context"
8+
"encoding/json"
9+
"net/http"
10+
"net/url"
11+
"testing"
12+
"time"
13+
14+
"github.com/stretchr/testify/assert"
15+
"github.com/stretchr/testify/require"
16+
)
17+
18+
type Jwks struct {
19+
Keys []Key `json:"keys"`
20+
}
21+
22+
type Key struct {
23+
Kid string `json:"kid"`
24+
}
25+
26+
func TestAdminSettings_Oidc_RotateKey(t *testing.T) {
27+
skipUnlessEnterprise(t)
28+
29+
client := testClient(t)
30+
jwksClient := http.Client{
31+
Timeout: time.Second * 2,
32+
}
33+
baseURL := client.baseURL
34+
token := client.token
35+
36+
ctx := context.Background()
37+
38+
jwks, err := getJwks(jwksClient, baseURL, token)
39+
require.NoError(t, err)
40+
41+
// Don't assume there is only 1 key to start
42+
originalNumKeys := len(jwks.Keys)
43+
44+
err = client.Admin.Settings.OIDC.RotateKey(ctx)
45+
require.NoError(t, err)
46+
47+
jwks, err = getJwks(jwksClient, baseURL, token)
48+
require.NoError(t, err)
49+
50+
newNumKeys := len(jwks.Keys)
51+
52+
// Rotate should add 1 additional key
53+
assert.Equal(t, originalNumKeys+1, newNumKeys)
54+
}
55+
56+
func TestAdminSettings_Oidc_TrimKey(t *testing.T) {
57+
skipUnlessEnterprise(t)
58+
59+
client := testClient(t)
60+
jwksClient := http.Client{
61+
Timeout: time.Second * 2,
62+
}
63+
baseURL := client.baseURL
64+
token := client.token
65+
66+
ctx := context.Background()
67+
68+
jwks, err := getJwks(jwksClient, baseURL, token)
69+
require.NoError(t, err)
70+
71+
// Don't assume there is only 1 key to start
72+
originalNumKeys := len(jwks.Keys)
73+
74+
originalKids := make([]string, originalNumKeys)
75+
76+
for i := 0; i < originalNumKeys; i++ {
77+
originalKids[i] = jwks.Keys[i].Kid
78+
}
79+
80+
err = client.Admin.Settings.OIDC.RotateKey(ctx)
81+
require.NoError(t, err)
82+
83+
jwks, err = getJwks(jwksClient, baseURL, token)
84+
require.NoError(t, err)
85+
86+
beforeTrimNumKeys := len(jwks.Keys)
87+
88+
assert.Equal(t, originalNumKeys+1, beforeTrimNumKeys)
89+
90+
err = client.Admin.Settings.OIDC.TrimKey(ctx)
91+
require.NoError(t, err)
92+
93+
jwks, err = getJwks(jwksClient, baseURL, token)
94+
require.NoError(t, err)
95+
96+
afterTrimNumKeys := len(jwks.Keys)
97+
98+
assert.Equal(t, 1, afterTrimNumKeys)
99+
100+
// Make sure we actually trimmed the keys
101+
assert.NotContains(t, originalKids, jwks.Keys[0].Kid)
102+
}
103+
104+
func getJwks(client http.Client, baseURL *url.URL, token string) (*Jwks, error) {
105+
jwksEndpoint, err := baseURL.Parse("/.well-known/jwks")
106+
if err != nil {
107+
return nil, err
108+
}
109+
110+
req, err := http.NewRequest(http.MethodGet, jwksEndpoint.String(), nil)
111+
if err != nil {
112+
return nil, err
113+
}
114+
115+
req.Header.Set("Authorization", "Bearer "+token)
116+
req.Header.Set("Accept", "application/json")
117+
118+
res, getErr := client.Do(req)
119+
if getErr != nil {
120+
return nil, getErr
121+
}
122+
123+
if res.Body != nil {
124+
defer res.Body.Close()
125+
}
126+
127+
var result Jwks
128+
jsonErr := json.NewDecoder(res.Body).Decode(&result)
129+
if jsonErr != nil {
130+
return nil, jsonErr
131+
}
132+
133+
return &result, nil
134+
}

0 commit comments

Comments
 (0)