Skip to content

Commit 6ff1e25

Browse files
committed
Merge branch 'main' into vault-27421-update-cap-ldap-dep
2 parents adbad9e + 27bd3e9 commit 6ff1e25

File tree

60 files changed

+2602
-1415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+2602
-1415
lines changed

builtin/credential/ldap/backend.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,14 +121,12 @@ func (b *backend) Login(ctx context.Context, req *logical.Request, username stri
121121
if b.Logger().IsDebug() {
122122
b.Logger().Debug(errString)
123123
}
124-
ldapResponse.AddWarning(errString)
125124
}
126125

127126
for _, warning := range c.Warnings {
128127
if b.Logger().IsDebug() {
129128
b.Logger().Debug(string(warning))
130129
}
131-
ldapResponse.AddWarning(string(warning))
132130
}
133131

134132
var allGroups []string

builtin/credential/ldap/backend_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1183,8 +1183,8 @@ func testAccStepLoginNoGroupDN(t *testing.T, user string, pass string) logicalte
11831183

11841184
// Verifies a search without defined GroupDN returns a warning rather than failing
11851185
Check: func(resp *logical.Response) error {
1186-
if len(resp.Warnings) != 1 {
1187-
return fmt.Errorf("expected a warning due to no group dn, got: %#v", resp.Warnings)
1186+
if len(resp.Warnings) != 0 {
1187+
return fmt.Errorf("expected a no warnings, got: %#v", resp.Warnings)
11881188
}
11891189

11901190
return logicaltest.TestCheckAuth([]string{"bar", "default"})(resp)

builtin/logical/aws/backend.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"sync"
1111
"time"
1212

13+
"github.com/aws/aws-sdk-go/aws"
14+
"github.com/aws/aws-sdk-go/service/iam"
1315
"github.com/aws/aws-sdk-go/service/iam/iamiface"
1416
"github.com/aws/aws-sdk-go/service/sts/stsiface"
1517
"github.com/hashicorp/vault/sdk/framework"
@@ -74,6 +76,91 @@ func Backend(_ *logical.BackendConfig) *backend {
7476
}
7577
return nil
7678
},
79+
RotateCredential: func(ctx context.Context, req *logical.Request) error {
80+
// the following code is a modified version of the rotate-root method
81+
client, err := b.clientIAM(ctx, req.Storage)
82+
if err != nil {
83+
return err
84+
}
85+
if client == nil {
86+
return fmt.Errorf("nil IAM client")
87+
}
88+
89+
b.clientMutex.Lock()
90+
defer b.clientMutex.Unlock()
91+
92+
rawRootConfig, err := req.Storage.Get(ctx, "config/root")
93+
if err != nil {
94+
return err
95+
}
96+
if rawRootConfig == nil {
97+
return fmt.Errorf("no configuration found for config/root")
98+
}
99+
var config rootConfig
100+
if err := rawRootConfig.DecodeJSON(&config); err != nil {
101+
return fmt.Errorf("error reading root configuration: %w", err)
102+
}
103+
104+
if config.AccessKey == "" || config.SecretKey == "" {
105+
return fmt.Errorf("cannot call config/rotate-root when either access_key or secret_key is empty")
106+
}
107+
108+
var getUserInput iam.GetUserInput // empty input means get current user
109+
getUserRes, err := client.GetUserWithContext(ctx, &getUserInput)
110+
if err != nil {
111+
return fmt.Errorf("error calling GetUser: %w", err)
112+
}
113+
if getUserRes == nil {
114+
return fmt.Errorf("nil response from GetUser")
115+
}
116+
if getUserRes.User == nil {
117+
return fmt.Errorf("nil user returned from GetUser")
118+
}
119+
if getUserRes.User.UserName == nil {
120+
return fmt.Errorf("nil UserName returned from GetUser")
121+
}
122+
123+
createAccessKeyInput := iam.CreateAccessKeyInput{
124+
UserName: getUserRes.User.UserName,
125+
}
126+
createAccessKeyRes, err := client.CreateAccessKeyWithContext(ctx, &createAccessKeyInput)
127+
if err != nil {
128+
return fmt.Errorf("error calling CreateAccessKey: %w", err)
129+
}
130+
if createAccessKeyRes.AccessKey == nil {
131+
return fmt.Errorf("nil response from CreateAccessKey")
132+
}
133+
if createAccessKeyRes.AccessKey.AccessKeyId == nil || createAccessKeyRes.AccessKey.SecretAccessKey == nil {
134+
return fmt.Errorf("nil AccessKeyId or SecretAccessKey returned from CreateAccessKey")
135+
}
136+
137+
oldAccessKey := config.AccessKey
138+
139+
config.AccessKey = *createAccessKeyRes.AccessKey.AccessKeyId
140+
config.SecretKey = *createAccessKeyRes.AccessKey.SecretAccessKey
141+
142+
newEntry, err := logical.StorageEntryJSON("config/root", config)
143+
if err != nil {
144+
return fmt.Errorf("error generating new config/root JSON: %w", err)
145+
}
146+
if err := req.Storage.Put(ctx, newEntry); err != nil {
147+
return fmt.Errorf("error saving new config/root: %w", err)
148+
}
149+
150+
b.iamClient = nil
151+
b.stsClient = nil
152+
153+
deleteAccessKeyInput := iam.DeleteAccessKeyInput{
154+
AccessKeyId: aws.String(oldAccessKey),
155+
UserName: getUserRes.User.UserName,
156+
}
157+
_, err = client.DeleteAccessKeyWithContext(ctx, &deleteAccessKeyInput)
158+
if err != nil {
159+
return fmt.Errorf("error deleting old access key: %w", err)
160+
}
161+
162+
return nil
163+
},
77164
BackendType: logical.TypeLogical,
78165
}
79166

builtin/logical/aws/path_config_root.go

Lines changed: 98 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,18 @@ import (
99

1010
"github.com/aws/aws-sdk-go/aws"
1111
"github.com/hashicorp/vault/sdk/framework"
12+
"github.com/hashicorp/vault/sdk/helper/automatedrotationutil"
1213
"github.com/hashicorp/vault/sdk/helper/pluginidentityutil"
1314
"github.com/hashicorp/vault/sdk/helper/pluginutil"
1415
"github.com/hashicorp/vault/sdk/logical"
16+
"github.com/hashicorp/vault/sdk/rotation"
1517
)
1618

1719
// A single default template that supports both the different credential types (IAM/STS) that are capped at differing length limits (64 chars/32 chars respectively)
18-
const defaultUserNameTemplate = `{{ if (eq .Type "STS") }}{{ printf "vault-%s-%s" (unix_time) (random 20) | truncate 32 }}{{ else }}{{ printf "vault-%s-%s-%s" (printf "%s-%s" (.DisplayName) (.PolicyName) | truncate 42) (unix_time) (random 20) | truncate 64 }}{{ end }}`
20+
const (
21+
defaultUserNameTemplate = `{{ if (eq .Type "STS") }}{{ printf "vault-%s-%s" (unix_time) (random 20) | truncate 32 }}{{ else }}{{ printf "vault-%s-%s-%s" (printf "%s-%s" (.DisplayName) (.PolicyName) | truncate 42) (unix_time) (random 20) | truncate 64 }}{{ end }}`
22+
rootRotationJobName = "aws-root-creds"
23+
)
1924

2025
func pathConfigRoot(b *backend) *framework.Path {
2126
p := &framework.Path{
@@ -95,6 +100,7 @@ func pathConfigRoot(b *backend) *framework.Path {
95100
HelpDescription: pathConfigRootHelpDesc,
96101
}
97102
pluginidentityutil.AddPluginIdentityTokenFields(p.Fields)
103+
automatedrotationutil.AddAutomatedRotationFields(p.Fields)
98104

99105
return p
100106
}
@@ -103,20 +109,14 @@ func (b *backend) pathConfigRootRead(ctx context.Context, req *logical.Request,
103109
b.clientMutex.RLock()
104110
defer b.clientMutex.RUnlock()
105111

106-
entry, err := req.Storage.Get(ctx, "config/root")
112+
config, exists, err := getConfigFromStorage(ctx, req)
107113
if err != nil {
108114
return nil, err
109115
}
110-
if entry == nil {
116+
if !exists {
111117
return nil, nil
112118
}
113119

114-
var config rootConfig
115-
116-
if err := entry.DecodeJSON(&config); err != nil {
117-
return nil, err
118-
}
119-
120120
configData := map[string]interface{}{
121121
"access_key": config.AccessKey,
122122
"region": config.Region,
@@ -131,6 +131,8 @@ func (b *backend) pathConfigRootRead(ctx context.Context, req *logical.Request,
131131
}
132132

133133
config.PopulatePluginIdentityTokenData(configData)
134+
config.PopulateAutomatedRotationData(configData)
135+
134136
return &logical.Response{
135137
Data: configData,
136138
}, nil
@@ -158,6 +160,12 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
158160
b.clientMutex.Lock()
159161
defer b.clientMutex.Unlock()
160162

163+
// check for existing config
164+
previousCfg, previousCfgExists, err := getConfigFromStorage(ctx, req)
165+
if err != nil {
166+
return nil, err
167+
}
168+
161169
rc := rootConfig{
162170
AccessKey: data.Get("access_key").(string),
163171
SecretKey: data.Get("secret_key").(string),
@@ -174,6 +182,9 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
174182
if err := rc.ParsePluginIdentityTokenFields(data); err != nil {
175183
return logical.ErrorResponse(err.Error()), nil
176184
}
185+
if err := rc.ParseAutomatedRotationFields(data); err != nil {
186+
return logical.ErrorResponse(err.Error()), nil
187+
}
177188

178189
if rc.IdentityTokenAudience != "" && rc.AccessKey != "" {
179190
return logical.ErrorResponse("only one of 'access_key' or 'identity_token_audience' can be set"), nil
@@ -195,12 +206,54 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
195206
}
196207
}
197208

198-
entry, err := logical.StorageEntryJSON("config/root", rc)
199-
if err != nil {
200-
return nil, err
209+
// Save the initial config only if it does not already exist
210+
if !previousCfgExists {
211+
if err := putConfigToStorage(ctx, req, &rc); err != nil {
212+
return nil, err
213+
}
201214
}
202215

203-
if err := req.Storage.Put(ctx, entry); err != nil {
216+
// Now that the root config is set up, register the rotation job if it required
217+
if rc.ShouldRegisterRotationJob() {
218+
cfgReq := &rotation.RotationJobConfigureRequest{
219+
Name: rootRotationJobName,
220+
MountPoint: req.MountPoint,
221+
ReqPath: req.Path,
222+
RotationSchedule: rc.RotationSchedule,
223+
RotationWindow: rc.RotationWindow,
224+
RotationPeriod: rc.RotationPeriod,
225+
}
226+
227+
rotationJob, err := rotation.ConfigureRotationJob(cfgReq)
228+
if err != nil {
229+
return logical.ErrorResponse("error configuring rotation job: %s", err), nil
230+
}
231+
232+
b.Logger().Debug("Registering rotation job", "mount", req.MountPoint+req.Path)
233+
rotationID, err := b.System().RegisterRotationJob(ctx, rotationJob)
234+
if err != nil {
235+
return logical.ErrorResponse("error registering rotation job: %s", err), nil
236+
}
237+
238+
rc.RotationID = rotationID
239+
}
240+
241+
// Disable Automated Rotation and Deregister credentials if required
242+
if rc.DisableAutomatedRotation {
243+
// Ensure de-registering only occurs on updates and if
244+
// a credential has actually been registered
245+
if previousCfgExists && previousCfg.RotationID != "" {
246+
err := b.System().DeregisterRotationJob(ctx, previousCfg.RotationID)
247+
if err != nil {
248+
return logical.ErrorResponse("error de-registering rotation job: %s", err), nil
249+
}
250+
251+
rc.RotationID = ""
252+
}
253+
}
254+
255+
// update config entry with rotation ID
256+
if err := putConfigToStorage(ctx, req, &rc); err != nil {
204257
return nil, err
205258
}
206259

@@ -212,8 +265,40 @@ func (b *backend) pathConfigRootWrite(ctx context.Context, req *logical.Request,
212265
return nil, nil
213266
}
214267

268+
func getConfigFromStorage(ctx context.Context, req *logical.Request) (*rootConfig, bool, error) {
269+
entry, err := req.Storage.Get(ctx, "config/root")
270+
if err != nil {
271+
return nil, false, err
272+
}
273+
if entry == nil {
274+
return nil, false, nil
275+
}
276+
277+
var config rootConfig
278+
279+
if err := entry.DecodeJSON(&config); err != nil {
280+
return nil, false, err
281+
}
282+
283+
return &config, true, nil
284+
}
285+
286+
func putConfigToStorage(ctx context.Context, req *logical.Request, rc *rootConfig) error {
287+
entry, err := logical.StorageEntryJSON("config/root", rc)
288+
if err != nil {
289+
return err
290+
}
291+
292+
if err := req.Storage.Put(ctx, entry); err != nil {
293+
return err
294+
}
295+
296+
return nil
297+
}
298+
215299
type rootConfig struct {
216300
pluginidentityutil.PluginIdentityTokenParams
301+
automatedrotationutil.AutomatedRotationParams
217302

218303
AccessKey string `json:"access_key"`
219304
SecretKey string `json:"secret_key"`

0 commit comments

Comments
 (0)