Skip to content

Commit 4ba2d4d

Browse files
committed
Adds support for adding attachments to requests with Common Fate using --attach
1 parent 8547465 commit 4ba2d4d

File tree

9 files changed

+98
-46
lines changed

9 files changed

+98
-46
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ require (
2929
github.com/common-fate/common-fate v0.15.13
3030
github.com/common-fate/glide-cli v0.6.0
3131
github.com/common-fate/grab v1.3.0
32-
github.com/common-fate/sdk v1.69.0
32+
github.com/common-fate/sdk v1.70.3-0.20241125053707-a6c1defd9189
3333
github.com/common-fate/xid v1.0.0
3434
github.com/fatih/color v1.16.0
3535
github.com/hashicorp/yamux v0.1.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ github.com/common-fate/iso8601 v1.1.0 h1:nrej9shsK1aB4IyOAjZl68xGk8yDuUxVwQjoDzx
8080
github.com/common-fate/iso8601 v1.1.0/go.mod h1:DU4mvUEkkWZUUSJq2aCuNqM1luSb0Pwyb2dLzXS+img=
8181
github.com/common-fate/sdk v1.69.0 h1:EcgIBjAFFvQnCd1/Lj5Wik/bOUMD9xhxDLEmXS1H7Gk=
8282
github.com/common-fate/sdk v1.69.0/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo=
83+
github.com/common-fate/sdk v1.70.3-0.20241125053707-a6c1defd9189 h1:1MdYrkF18no04kC/VRrr4mqAaQMzHDINJ4jxtDvnoyk=
84+
github.com/common-fate/sdk v1.70.3-0.20241125053707-a6c1defd9189/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo=
8385
github.com/common-fate/session-manager-plugin v0.0.0-20240723053832-3d311db99016 h1:WObxQKT/BuR8HWKSGsJ6aQb/cdhvkenkb1KWXNyPWeE=
8486
github.com/common-fate/session-manager-plugin v0.0.0-20240723053832-3d311db99016/go.mod h1:glAZTUB+4Eg0JVLC3B/YEomJv6QHcNS3klJjw9HC5Y8=
8587
github.com/common-fate/updatecheck v0.3.5 h1:UGIKMnYwuHjbhhCaisLz1pNPg8Z1nXEoWcfqT+4LkAg=

pkg/assume/assume.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ func AssumeCommand(c *cli.Context) error {
305305
}
306306

307307
reason := assumeFlags.String("reason")
308-
308+
attachments := assumeFlags.StringSlice("attach")
309309
cfg, err := config.Load()
310310
if err != nil {
311311
return err
@@ -350,12 +350,13 @@ func AssumeCommand(c *cli.Context) error {
350350
}
351351

352352
noAccessInput := accessrequesthook.NoAccessInput{
353-
Profile: profile,
354-
Reason: reason,
355-
Duration: apiDuration,
356-
Confirm: assumeFlags.Bool("confirm"),
357-
Wait: wait,
358-
StartTime: time.Now(),
353+
Profile: profile,
354+
Reason: reason,
355+
Attachments: attachments,
356+
Duration: apiDuration,
357+
Confirm: assumeFlags.Bool("confirm"),
358+
Wait: wait,
359+
StartTime: time.Now(),
359360
}
360361
retry, justActivated, hookErr := hook.NoAccess(c.Context, noAccessInput)
361362
if hookErr != nil {

pkg/assume/entrypoint.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func GlobalFlags() []cli.Flag {
5858
&cli.BoolFlag{Name: "no-cache", Usage: "Disables caching of session credentials and forces a refresh", EnvVars: []string{"GRANTED_NO_CACHE"}},
5959
&cli.StringSliceFlag{Name: "browser-launch-template-arg", Usage: "Additional arguments to provide to the browser launch template command in key=value format, e.g. '--browser-launch-template-arg foo=bar"},
6060
&cli.BoolFlag{Name: "skip-profile-registry-sync", Usage: "You can use this to skip the automated profile registry sync process."},
61+
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
6162
}
6263
}
6364

pkg/granted/eks/eks.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var proxyCommand = cli.Command{
3636
&cli.StringFlag{Name: "target", Aliases: []string{"cluster"}},
3737
&cli.StringFlag{Name: "role", Aliases: []string{"service-account"}},
3838
&cli.StringFlag{Name: "reason", Usage: "Provide a reason for requesting access to the role"},
39+
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
3940
&cli.BoolFlag{Name: "confirm", Aliases: []string{"y"}, Usage: "Skip confirmation prompts for access requests"},
4041
&cli.BoolFlag{Name: "wait", Value: true, Usage: "Wait for the access request to be approved."},
4142
&cli.BoolFlag{Name: "no-cache", Usage: "Disables caching of session credentials and forces a refresh", EnvVars: []string{"GRANTED_NO_CACHE"}},
@@ -59,6 +60,7 @@ var proxyCommand = cli.Command{
5960
Role: c.String("role"),
6061
Duration: c.Duration("duration"),
6162
Reason: c.String("reason"),
63+
Attachments: c.StringSlice("attach"),
6264
Confirm: c.Bool("confirm"),
6365
Wait: c.Bool("wait"),
6466
PromptForEntitlement: promptForClusterAndRole,

pkg/granted/proxy/ensureaccess.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type EnsureAccessInput[T any] struct {
2828
Role string
2929
Duration time.Duration
3030
Reason string
31+
Attachments []string
3132
Confirm bool
3233
Wait bool
3334
PromptForEntitlement func(ctx context.Context, cfg *config.Context) (*accessv1alpha1.Entitlement, error)
@@ -42,13 +43,14 @@ type EnsureAccessOutput[T any] struct {
4243
func EnsureAccess[T any](ctx context.Context, cfg *config.Context, input EnsureAccessInput[T]) (*EnsureAccessOutput[T], error) {
4344

4445
accessRequestInput := accessrequesthook.NoEntitlementAccessInput{
45-
Target: input.Target,
46-
Role: input.Role,
47-
Reason: input.Reason,
48-
Duration: durationOrDefault(input.Duration),
49-
Confirm: input.Confirm,
50-
Wait: input.Wait,
51-
StartTime: time.Now(),
46+
Target: input.Target,
47+
Role: input.Role,
48+
Reason: input.Reason,
49+
Attachments: input.Attachments,
50+
Duration: durationOrDefault(input.Duration),
51+
Confirm: input.Confirm,
52+
Wait: input.Wait,
53+
StartTime: time.Now(),
5254
}
5355

5456
if accessRequestInput.Target == "" && accessRequestInput.Role == "" {

pkg/granted/rds/rds.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ var proxyCommand = cli.Command{
3939
&cli.StringFlag{Name: "role", Aliases: []string{"user"}},
4040
&cli.IntFlag{Name: "port", Usage: "The local port to forward the database connection to"},
4141
&cli.StringFlag{Name: "reason", Usage: "Provide a reason for requesting access to the role"},
42+
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
4243
&cli.BoolFlag{Name: "confirm", Aliases: []string{"y"}, Usage: "Skip confirmation prompts for access requests"},
4344
&cli.BoolFlag{Name: "wait", Value: true, Usage: "Wait for the access request to be approved."},
4445
&cli.BoolFlag{Name: "no-cache", Usage: "Disables caching of session credentials and forces a refresh", EnvVars: []string{"GRANTED_NO_CACHE"}},
@@ -62,6 +63,7 @@ var proxyCommand = cli.Command{
6263
Role: c.String("role"),
6364
Duration: c.Duration("duration"),
6465
Reason: c.String("reason"),
66+
Attachments: c.StringSlice("attach"),
6567
Confirm: c.Bool("confirm"),
6668
Wait: c.Bool("wait"),
6769
PromptForEntitlement: promptForDatabaseAndUser,

pkg/granted/request/request.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ var latestCommand = cli.Command{
3636
Usage: "Request access to the latest AWS role you attempted to use",
3737
Flags: []cli.Flag{
3838
&cli.StringFlag{Name: "reason", Usage: "A reason for access"},
39+
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
3940
&cli.DurationFlag{Name: "duration", Usage: "Duration of request, defaults to max duration of the access rule."},
4041
&cli.BoolFlag{Name: "confirm", Aliases: []string{"y"}, Usage: "Skip confirmation prompts for access requests"},
4142
},
@@ -105,10 +106,11 @@ var latestCommand = cli.Command{
105106
}
106107

107108
_, _, err = hook.NoAccess(c.Context, accessrequesthook.NoAccessInput{
108-
Profile: profile,
109-
Reason: reason,
110-
Duration: apiDuration,
111-
Confirm: c.Bool("confirm"),
109+
Profile: profile,
110+
Reason: reason,
111+
Attachments: c.StringSlice("attach"),
112+
Duration: apiDuration,
113+
Confirm: c.Bool("confirm"),
112114
})
113115
if err != nil {
114116
return err

pkg/hook/accessrequesthook/accessrequesthook.go

Lines changed: 67 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414
"github.com/briandowns/spinner"
1515
"github.com/common-fate/cli/printdiags"
1616
"github.com/common-fate/clio"
17+
"github.com/common-fate/grab"
1718
"github.com/common-fate/granted/pkg/cfaws"
1819
"github.com/common-fate/granted/pkg/cfcfg"
1920
"github.com/common-fate/sdk/config"
@@ -31,12 +32,13 @@ import (
3132
type Hook struct{}
3233

3334
type NoAccessInput struct {
34-
Profile *cfaws.Profile
35-
Reason string
36-
Duration *durationpb.Duration
37-
Confirm bool
38-
Wait bool
39-
StartTime time.Time
35+
Profile *cfaws.Profile
36+
Reason string
37+
Attachments []string
38+
Duration *durationpb.Duration
39+
Confirm bool
40+
Wait bool
41+
StartTime time.Time
4042
}
4143

4244
func (h Hook) NoAccess(ctx context.Context, input NoAccessInput) (retry bool, justActivated bool, err error) {
@@ -53,26 +55,28 @@ func (h Hook) NoAccess(ctx context.Context, input NoAccessInput) (retry bool, ju
5355
clio.Infof("You don't currently have access to %s, checking if we can request access...\t[target=%s, role=%s, url=%s]", input.Profile.Name, target, role, cfg.AccessURL)
5456

5557
retry, _, justActivated, err = h.NoEntitlementAccess(ctx, cfg, NoEntitlementAccessInput{
56-
Target: target.String(),
57-
Role: role,
58-
Reason: input.Reason,
59-
Duration: input.Duration,
60-
Confirm: input.Confirm,
61-
Wait: input.Wait,
62-
StartTime: input.StartTime,
58+
Target: target.String(),
59+
Role: role,
60+
Reason: input.Reason,
61+
Duration: input.Duration,
62+
Confirm: input.Confirm,
63+
Wait: input.Wait,
64+
StartTime: input.StartTime,
65+
Attachments: input.Attachments,
6366
})
6467

6568
return retry, justActivated, err
6669
}
6770

6871
type NoEntitlementAccessInput struct {
69-
Target string
70-
Role string
71-
Reason string
72-
Duration *durationpb.Duration
73-
Confirm bool
74-
Wait bool
75-
StartTime time.Time
72+
Target string
73+
Role string
74+
Reason string
75+
Attachments []string
76+
Duration *durationpb.Duration
77+
Confirm bool
78+
Wait bool
79+
StartTime time.Time
7680
}
7781

7882
func (h Hook) NoEntitlementAccess(ctx context.Context, cfg *config.Context, input NoEntitlementAccessInput) (retry bool, result *accessv1alpha1.BatchEnsureResponse, justActivated bool, err error) {
@@ -167,6 +171,41 @@ func (h Hook) NoEntitlementAccess(ctx context.Context, cfg *config.Context, inpu
167171
}
168172
}
169173

174+
if len(input.Attachments) > 0 {
175+
req.Justification.Attachments = grab.Map(input.Attachments, func(t string) *accessv1alpha1.AttachmentSpecifier {
176+
return &accessv1alpha1.AttachmentSpecifier{
177+
Specify: &accessv1alpha1.AttachmentSpecifier_Lookup{
178+
Lookup: t,
179+
},
180+
}
181+
})
182+
} else {
183+
if result.Validation != nil && result.Validation.HasJiraTicket {
184+
if !IsTerminal(os.Stdin.Fd()) {
185+
return false, nil, justActivated, errors.New("detected a noninteractive terminal: a jira ticket attachment is required to make this access request, to apply the planned changes please re-run with the --attach flag")
186+
}
187+
188+
var attachment string
189+
msg := "Jira ticket attachment for access (Required)"
190+
reasonPrompt := &survey.Input{
191+
Message: msg,
192+
Help: "Will be stored in audit trails and associated with your request",
193+
}
194+
withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)
195+
err = survey.AskOne(reasonPrompt, &attachment, withStdio, survey.WithValidator(survey.Required))
196+
197+
if err != nil {
198+
return false, nil, justActivated, err
199+
}
200+
201+
req.Justification.Attachments = append(req.Justification.Attachments, &accessv1alpha1.AttachmentSpecifier{
202+
Specify: &accessv1alpha1.AttachmentSpecifier_Lookup{
203+
Lookup: attachment,
204+
},
205+
})
206+
}
207+
}
208+
170209
// the spinner must be started after prompting for reason, otherwise the prompt gets hidden
171210
si := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
172211
si.Suffix = " ensuring access..."
@@ -276,13 +315,14 @@ func (h Hook) RetryAccess(ctx context.Context, input NoAccessInput) error {
276315
target := eid.New("AWS::Account", input.Profile.AWSConfig.SSOAccountID)
277316
role := input.Profile.AWSConfig.SSORoleName
278317
_, err = h.RetryNoEntitlementAccess(ctx, cfg, NoEntitlementAccessInput{
279-
Target: target.String(),
280-
Role: role,
281-
Reason: input.Reason,
282-
Duration: input.Duration,
283-
Confirm: input.Confirm,
284-
Wait: input.Wait,
285-
StartTime: input.StartTime,
318+
Target: target.String(),
319+
Role: role,
320+
Reason: input.Reason,
321+
Duration: input.Duration,
322+
Confirm: input.Confirm,
323+
Wait: input.Wait,
324+
StartTime: input.StartTime,
325+
Attachments: input.Attachments,
286326
})
287327
return err
288328
}

0 commit comments

Comments
 (0)