Skip to content

Commit 6907298

Browse files
akerl-unprivakerl
authored andcommitted
Feature/multi path (#10)
* add multi-path cartogram support * adjust role formatting * update the things * fix typo * functional multipathing * fix linting * support custom lookup * all passthrough Store * support optional fallback * re-add dead-end check * profile inversion * fix cops * add check * check rather than lookup * properly filter paths * use slices for role/profile selection * clean up file * fix cops * fix cops for real * fix line length
1 parent 259b57c commit 6907298

File tree

11 files changed

+538
-337
lines changed

11 files changed

+538
-337
lines changed

cartogram/account.go

+21-47
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,29 @@
11
package cartogram
22

3-
import (
4-
"fmt"
5-
"sort"
6-
7-
"github.com/akerl/voyager/prompt"
8-
)
9-
103
// AccountSet is a set of accounts
114
type AccountSet []Account
125

136
// Account defines the spec for a role assumption target
147
type Account struct {
15-
Account string `json:"account"`
16-
Region string `json:"region"`
17-
Source string `json:"source"`
18-
Roles map[string]Role `json:"roles"`
19-
Tags Tags `json:"tags"`
8+
Account string `json:"account"`
9+
Region string `json:"region"`
10+
Roles RoleSet `json:"roles"`
11+
Tags Tags `json:"tags"`
2012
}
2113

14+
// RoleSet is a list of Roles
15+
type RoleSet []Role
16+
2217
// Role holds information about authenticating to a role
2318
type Role struct {
24-
Mfa bool `json:"mfa"`
19+
Name string `json:"name"`
20+
Mfa bool `json:"mfa"`
21+
Sources []Source `json:"sources"`
22+
}
23+
24+
// Source defines the previous hop for accessing a role
25+
type Source struct {
26+
Path string `json:"path"`
2527
}
2628

2729
// Lookup finds an account in a Cartogram based on its ID
@@ -45,40 +47,12 @@ func (as AccountSet) Search(tfs TagFilterSet) AccountSet {
4547
return results
4648
}
4749

48-
// PickRole returns a role from the account
49-
func (a Account) PickRole(roleName string) (string, error) {
50-
return a.PickRoleWithPrompt(roleName, prompt.WithDefault)
51-
}
52-
53-
// PickRoleWithPrompt returns a role from the account with a custom prompt
54-
func (a Account) PickRoleWithPrompt(roleName string, pf prompt.Func) (string, error) {
55-
if roleName != "" {
56-
if _, ok := a.Roles[roleName]; !ok {
57-
return "", fmt.Errorf("provided role not present in account")
50+
// Lookup searches for a role by name
51+
func (rs RoleSet) Lookup(name string) (bool, Role) {
52+
for _, r := range rs {
53+
if r.Name == name {
54+
return true, r
5855
}
59-
return roleName, nil
6056
}
61-
roleNames := []string{}
62-
for k := range a.Roles {
63-
roleNames = append(roleNames, k)
64-
}
65-
if len(roleNames) == 1 {
66-
return roleNames[0], nil
67-
}
68-
sort.Strings(roleNames)
69-
roleSlices := [][]string{}
70-
for _, k := range roleNames {
71-
roleSlices = append(roleSlices, []string{k})
72-
}
73-
74-
pa := prompt.Args{
75-
Message: "Desired Role:",
76-
Options: roleSlices,
77-
}
78-
index, err := pf(pa)
79-
if err != nil {
80-
return "", err
81-
}
82-
83-
return roleNames[index], nil
57+
return false, Role{}
8458
}

cartogram/main.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import (
88

99
const (
1010
configName = ".cartograms"
11-
specVersion = 1
11+
specVersion = 2
1212
)
1313

1414
func configDir() (string, error) {

cmd/travel.go

+12-5
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ var travelCmd = &cobra.Command{
1717

1818
func init() {
1919
rootCmd.AddCommand(travelCmd)
20-
travelCmd.Flags().StringP("role", "r", "", "Choose role to use")
20+
travelCmd.Flags().StringP("role", "r", "", "Choose target role to use")
21+
travelCmd.Flags().String("profile", "", "Choose source profile to use")
2122
travelCmd.Flags().StringP("prompt", "p", "", "Choose prompt to use")
2223
travelCmd.Flags().BoolP("yubikey", "y", false, "Use Yubikey for MFA")
2324
}
@@ -30,6 +31,11 @@ func travelRunner(cmd *cobra.Command, args []string) error {
3031
return err
3132
}
3233

34+
flagProfile, err := flags.GetString("profile")
35+
if err != nil {
36+
return err
37+
}
38+
3339
promptFlag, err := flags.GetString("prompt")
3440
if err != nil {
3541
return err
@@ -45,16 +51,17 @@ func travelRunner(cmd *cobra.Command, args []string) error {
4551
}
4652

4753
i := travel.Itinerary{
48-
Args: args,
49-
RoleName: flagRole,
50-
Prompt: promptFunc,
54+
Args: args,
55+
RoleName: []string{flagRole},
56+
ProfileName: []string{flagProfile},
57+
Prompt: promptFunc,
5158
}
5259

5360
if useYubikey {
5461
i.MfaPrompt = yubikey.NewPrompt()
5562
}
5663

57-
creds, err := travel.Travel(i)
64+
creds, err := i.Travel()
5865
if err != nil {
5966
return err
6067
}

go.sum

+1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T
3535
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
3636
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM=
3737
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
38+
github.com/keybase/go-keychain v0.0.0-20190604185112-cc436cc9fe98 h1:CIcvKEAP7i7v/SWSwzAvq1ATWWs4+J/ezHqZT116+JA=
3839
github.com/keybase/go-keychain v0.0.0-20190604185112-cc436cc9fe98/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
3940
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
4041
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=

profiles/keyring.go

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package profiles
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
7+
"github.com/99designs/keyring"
8+
"github.com/aws/aws-sdk-go/aws/credentials"
9+
)
10+
11+
// KeyringStore fetches credentials from the system keyring
12+
type KeyringStore struct {
13+
Name string
14+
}
15+
16+
// Lookup checks the keyring for credentials
17+
func (k *KeyringStore) Lookup(profile string) (credentials.Value, error) {
18+
ring, err := k.keyring()
19+
if err != nil {
20+
return credentials.Value{}, err
21+
}
22+
itemName := k.itemName(profile)
23+
logger.InfoMsg(fmt.Sprintf("looking up in keyring: %s", itemName))
24+
item, err := ring.Get(itemName)
25+
if err != nil {
26+
return credentials.Value{}, err
27+
}
28+
return k.parseItem(item)
29+
}
30+
31+
// Write caches the credentials for the user
32+
func (k *KeyringStore) Write(profile string, creds credentials.Value) error {
33+
is := itemStruct{EnvVars: map[string]string{
34+
"AWS_ACCESS_KEY_ID": creds.AccessKeyID,
35+
"AWS_SECRET_ACCESS_KEY": creds.SecretAccessKey,
36+
}}
37+
data, err := json.Marshal(is)
38+
if err != nil {
39+
return err
40+
}
41+
logger.InfoMsg("storing profile in keyring")
42+
ring, err := k.keyring()
43+
if err != nil {
44+
return err
45+
}
46+
itemName := k.itemName(profile)
47+
return ring.Set(keyring.Item{
48+
Key: itemName,
49+
Label: itemName,
50+
Data: data,
51+
})
52+
}
53+
54+
// Check returns if the credentials are cached in the keyring
55+
func (k *KeyringStore) Check(profile string) bool {
56+
res, _ := k.Lookup(profile)
57+
return res.AccessKeyID != ""
58+
}
59+
60+
func (k *KeyringStore) config() keyring.Config {
61+
return keyring.Config{
62+
AllowedBackends: []keyring.BackendType{
63+
"keychain",
64+
"wincred",
65+
"file",
66+
},
67+
KeychainName: "login",
68+
KeychainTrustApplication: true,
69+
FilePasswordFunc: filePasswordShim,
70+
FileDir: "~/.voyager/" + k.getName(),
71+
ServiceName: "voyager:" + k.getName(),
72+
}
73+
}
74+
75+
func (k *KeyringStore) getName() string {
76+
if k.Name == "" {
77+
logger.InfoMsg(fmt.Sprintf("set keyring store to default"))
78+
k.Name = "default"
79+
}
80+
return k.Name
81+
}
82+
83+
type itemStruct struct {
84+
EnvVars map[string]string
85+
}
86+
87+
func (k *KeyringStore) parseItem(item keyring.Item) (credentials.Value, error) {
88+
is := itemStruct{}
89+
err := json.Unmarshal(item.Data, &is)
90+
if err != nil {
91+
return credentials.Value{}, err
92+
}
93+
return credentials.Value{
94+
AccessKeyID: is.EnvVars["AWS_ACCESS_KEY_ID"],
95+
SecretAccessKey: is.EnvVars["AWS_SECRET_ACCESS_KEY"],
96+
}, nil
97+
}
98+
99+
func (k *KeyringStore) itemName(profile string) string {
100+
return fmt.Sprintf("voyager:%s:profile:%s", k.getName(), profile)
101+
}
102+
103+
func (k *KeyringStore) keyring() (keyring.Keyring, error) {
104+
return keyring.Open(k.config())
105+
}
106+
107+
func filePasswordShim(_ string) (string, error) {
108+
return "", nil
109+
}

0 commit comments

Comments
 (0)