Skip to content

Commit

Permalink
Josh/cf 3767 add cli flag to attach jira ticket in assume (#808)
Browse files Browse the repository at this point in the history
* Adds support for adding attachments to requests with Common Fate using --attach

* bump SDK to v1.71.0
  • Loading branch information
JoshuaWilkes authored Nov 27, 2024
1 parent e047104 commit 91e5e76
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 46 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ require (
github.com/common-fate/common-fate v0.15.13
github.com/common-fate/glide-cli v0.6.0
github.com/common-fate/grab v1.3.0
github.com/common-fate/sdk v1.69.0
github.com/common-fate/sdk v1.71.0
github.com/common-fate/xid v1.0.0
github.com/fatih/color v1.16.0
github.com/hashicorp/yamux v0.1.2
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ github.com/common-fate/iso8601 v1.1.0 h1:nrej9shsK1aB4IyOAjZl68xGk8yDuUxVwQjoDzx
github.com/common-fate/iso8601 v1.1.0/go.mod h1:DU4mvUEkkWZUUSJq2aCuNqM1luSb0Pwyb2dLzXS+img=
github.com/common-fate/sdk v1.69.0 h1:EcgIBjAFFvQnCd1/Lj5Wik/bOUMD9xhxDLEmXS1H7Gk=
github.com/common-fate/sdk v1.69.0/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo=
github.com/common-fate/sdk v1.70.3-0.20241125053707-a6c1defd9189 h1:1MdYrkF18no04kC/VRrr4mqAaQMzHDINJ4jxtDvnoyk=
github.com/common-fate/sdk v1.70.3-0.20241125053707-a6c1defd9189/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo=
github.com/common-fate/sdk v1.71.0 h1:SA+KZdbkOWBR6SrTculoUlALAGj6ftULdUPgr3Yw7RY=
github.com/common-fate/sdk v1.71.0/go.mod h1:OrXhzB2Y1JSrKGHrb4qRmY+6MF2M3MFb+3edBnessXo=
github.com/common-fate/session-manager-plugin v0.0.0-20240723053832-3d311db99016 h1:WObxQKT/BuR8HWKSGsJ6aQb/cdhvkenkb1KWXNyPWeE=
github.com/common-fate/session-manager-plugin v0.0.0-20240723053832-3d311db99016/go.mod h1:glAZTUB+4Eg0JVLC3B/YEomJv6QHcNS3klJjw9HC5Y8=
github.com/common-fate/updatecheck v0.3.5 h1:UGIKMnYwuHjbhhCaisLz1pNPg8Z1nXEoWcfqT+4LkAg=
Expand Down
15 changes: 8 additions & 7 deletions pkg/assume/assume.go
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,7 @@ func AssumeCommand(c *cli.Context) error {
}

reason := assumeFlags.String("reason")

attachments := assumeFlags.StringSlice("attach")
cfg, err := config.Load()
if err != nil {
return err
Expand Down Expand Up @@ -350,12 +350,13 @@ func AssumeCommand(c *cli.Context) error {
}

noAccessInput := accessrequesthook.NoAccessInput{
Profile: profile,
Reason: reason,
Duration: apiDuration,
Confirm: assumeFlags.Bool("confirm"),
Wait: wait,
StartTime: time.Now(),
Profile: profile,
Reason: reason,
Attachments: attachments,
Duration: apiDuration,
Confirm: assumeFlags.Bool("confirm"),
Wait: wait,
StartTime: time.Now(),
}
retry, justActivated, hookErr := hook.NoAccess(c.Context, noAccessInput)
if hookErr != nil {
Expand Down
1 change: 1 addition & 0 deletions pkg/assume/entrypoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ func GlobalFlags() []cli.Flag {
&cli.BoolFlag{Name: "no-cache", Usage: "Disables caching of session credentials and forces a refresh", EnvVars: []string{"GRANTED_NO_CACHE"}},
&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"},
&cli.BoolFlag{Name: "skip-profile-registry-sync", Usage: "You can use this to skip the automated profile registry sync process."},
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
}
}

Expand Down
2 changes: 2 additions & 0 deletions pkg/granted/eks/eks.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var proxyCommand = cli.Command{
&cli.StringFlag{Name: "target", Aliases: []string{"cluster"}},
&cli.StringFlag{Name: "role", Aliases: []string{"service-account"}},
&cli.StringFlag{Name: "reason", Usage: "Provide a reason for requesting access to the role"},
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
&cli.BoolFlag{Name: "confirm", Aliases: []string{"y"}, Usage: "Skip confirmation prompts for access requests"},
&cli.BoolFlag{Name: "wait", Value: true, Usage: "Wait for the access request to be approved."},
&cli.BoolFlag{Name: "no-cache", Usage: "Disables caching of session credentials and forces a refresh", EnvVars: []string{"GRANTED_NO_CACHE"}},
Expand All @@ -59,6 +60,7 @@ var proxyCommand = cli.Command{
Role: c.String("role"),
Duration: c.Duration("duration"),
Reason: c.String("reason"),
Attachments: c.StringSlice("attach"),
Confirm: c.Bool("confirm"),
Wait: c.Bool("wait"),
PromptForEntitlement: promptForClusterAndRole,
Expand Down
16 changes: 9 additions & 7 deletions pkg/granted/proxy/ensureaccess.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ type EnsureAccessInput[T any] struct {
Role string
Duration time.Duration
Reason string
Attachments []string
Confirm bool
Wait bool
PromptForEntitlement func(ctx context.Context, cfg *config.Context) (*accessv1alpha1.Entitlement, error)
Expand All @@ -42,13 +43,14 @@ type EnsureAccessOutput[T any] struct {
func EnsureAccess[T any](ctx context.Context, cfg *config.Context, input EnsureAccessInput[T]) (*EnsureAccessOutput[T], error) {

accessRequestInput := accessrequesthook.NoEntitlementAccessInput{
Target: input.Target,
Role: input.Role,
Reason: input.Reason,
Duration: durationOrDefault(input.Duration),
Confirm: input.Confirm,
Wait: input.Wait,
StartTime: time.Now(),
Target: input.Target,
Role: input.Role,
Reason: input.Reason,
Attachments: input.Attachments,
Duration: durationOrDefault(input.Duration),
Confirm: input.Confirm,
Wait: input.Wait,
StartTime: time.Now(),
}

if accessRequestInput.Target == "" && accessRequestInput.Role == "" {
Expand Down
2 changes: 2 additions & 0 deletions pkg/granted/rds/rds.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var proxyCommand = cli.Command{
&cli.StringFlag{Name: "role", Aliases: []string{"user"}},
&cli.IntFlag{Name: "port", Usage: "The local port to forward the database connection to"},
&cli.StringFlag{Name: "reason", Usage: "Provide a reason for requesting access to the role"},
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
&cli.BoolFlag{Name: "confirm", Aliases: []string{"y"}, Usage: "Skip confirmation prompts for access requests"},
&cli.BoolFlag{Name: "wait", Value: true, Usage: "Wait for the access request to be approved."},
&cli.BoolFlag{Name: "no-cache", Usage: "Disables caching of session credentials and forces a refresh", EnvVars: []string{"GRANTED_NO_CACHE"}},
Expand All @@ -62,6 +63,7 @@ var proxyCommand = cli.Command{
Role: c.String("role"),
Duration: c.Duration("duration"),
Reason: c.String("reason"),
Attachments: c.StringSlice("attach"),
Confirm: c.Bool("confirm"),
Wait: c.Bool("wait"),
PromptForEntitlement: promptForDatabaseAndUser,
Expand Down
10 changes: 6 additions & 4 deletions pkg/granted/request/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var latestCommand = cli.Command{
Usage: "Request access to the latest AWS role you attempted to use",
Flags: []cli.Flag{
&cli.StringFlag{Name: "reason", Usage: "A reason for access"},
&cli.StringSliceFlag{Name: "attach", Usage: "Attach justifications to your request, such as a Jira ticket id or url `--attach=TP-123`"},
&cli.DurationFlag{Name: "duration", Usage: "Duration of request, defaults to max duration of the access rule."},
&cli.BoolFlag{Name: "confirm", Aliases: []string{"y"}, Usage: "Skip confirmation prompts for access requests"},
},
Expand Down Expand Up @@ -105,10 +106,11 @@ var latestCommand = cli.Command{
}

_, _, err = hook.NoAccess(c.Context, accessrequesthook.NoAccessInput{
Profile: profile,
Reason: reason,
Duration: apiDuration,
Confirm: c.Bool("confirm"),
Profile: profile,
Reason: reason,
Attachments: c.StringSlice("attach"),
Duration: apiDuration,
Confirm: c.Bool("confirm"),
})
if err != nil {
return err
Expand Down
94 changes: 67 additions & 27 deletions pkg/hook/accessrequesthook/accessrequesthook.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/briandowns/spinner"
"github.com/common-fate/cli/printdiags"
"github.com/common-fate/clio"
"github.com/common-fate/grab"
"github.com/common-fate/granted/pkg/cfaws"
"github.com/common-fate/granted/pkg/cfcfg"
"github.com/common-fate/sdk/config"
Expand All @@ -31,12 +32,13 @@ import (
type Hook struct{}

type NoAccessInput struct {
Profile *cfaws.Profile
Reason string
Duration *durationpb.Duration
Confirm bool
Wait bool
StartTime time.Time
Profile *cfaws.Profile
Reason string
Attachments []string
Duration *durationpb.Duration
Confirm bool
Wait bool
StartTime time.Time
}

func (h Hook) NoAccess(ctx context.Context, input NoAccessInput) (retry bool, justActivated bool, err error) {
Expand All @@ -53,26 +55,28 @@ func (h Hook) NoAccess(ctx context.Context, input NoAccessInput) (retry bool, ju
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)

retry, _, justActivated, err = h.NoEntitlementAccess(ctx, cfg, NoEntitlementAccessInput{
Target: target.String(),
Role: role,
Reason: input.Reason,
Duration: input.Duration,
Confirm: input.Confirm,
Wait: input.Wait,
StartTime: input.StartTime,
Target: target.String(),
Role: role,
Reason: input.Reason,
Duration: input.Duration,
Confirm: input.Confirm,
Wait: input.Wait,
StartTime: input.StartTime,
Attachments: input.Attachments,
})

return retry, justActivated, err
}

type NoEntitlementAccessInput struct {
Target string
Role string
Reason string
Duration *durationpb.Duration
Confirm bool
Wait bool
StartTime time.Time
Target string
Role string
Reason string
Attachments []string
Duration *durationpb.Duration
Confirm bool
Wait bool
StartTime time.Time
}

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

if len(input.Attachments) > 0 {
req.Justification.Attachments = grab.Map(input.Attachments, func(t string) *accessv1alpha1.AttachmentSpecifier {
return &accessv1alpha1.AttachmentSpecifier{
Specify: &accessv1alpha1.AttachmentSpecifier_Lookup{
Lookup: t,
},
}
})
} else {
if result.Validation != nil && result.Validation.HasJiraTicket {
if !IsTerminal(os.Stdin.Fd()) {
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")
}

var attachment string
msg := "Jira ticket attachment for access (Required)"
reasonPrompt := &survey.Input{
Message: msg,
Help: "Will be stored in audit trails and associated with your request",
}
withStdio := survey.WithStdio(os.Stdin, os.Stderr, os.Stderr)
err = survey.AskOne(reasonPrompt, &attachment, withStdio, survey.WithValidator(survey.Required))

if err != nil {
return false, nil, justActivated, err
}

req.Justification.Attachments = append(req.Justification.Attachments, &accessv1alpha1.AttachmentSpecifier{
Specify: &accessv1alpha1.AttachmentSpecifier_Lookup{
Lookup: attachment,
},
})
}
}

// the spinner must be started after prompting for reason, otherwise the prompt gets hidden
si := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
si.Suffix = " ensuring access..."
Expand Down Expand Up @@ -276,13 +315,14 @@ func (h Hook) RetryAccess(ctx context.Context, input NoAccessInput) error {
target := eid.New("AWS::Account", input.Profile.AWSConfig.SSOAccountID)
role := input.Profile.AWSConfig.SSORoleName
_, err = h.RetryNoEntitlementAccess(ctx, cfg, NoEntitlementAccessInput{
Target: target.String(),
Role: role,
Reason: input.Reason,
Duration: input.Duration,
Confirm: input.Confirm,
Wait: input.Wait,
StartTime: input.StartTime,
Target: target.String(),
Role: role,
Reason: input.Reason,
Duration: input.Duration,
Confirm: input.Confirm,
Wait: input.Wait,
StartTime: input.StartTime,
Attachments: input.Attachments,
})
return err
}
Expand Down

0 comments on commit 91e5e76

Please sign in to comment.