-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathawscfg.go
166 lines (141 loc) · 4.98 KB
/
awscfg.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
// Package awsconfigfile contains logic to template ~/.aws/config files
// based on Common Fate access rules.
package awsconfigfile
import (
"bytes"
"sort"
"strings"
"text/template"
"github.com/Masterminds/sprig/v3"
"gopkg.in/ini.v1"
)
type SSOProfile struct {
// SSO details
SSOStartURL string
SSORegion string
// Account and role details
Region string
AccountID string
AccountName string
RoleName string
CommonFateURL string
// GeneratedFrom is the source that the profile
// was created from, such as 'commonfate' or 'aws-sso'
GeneratedFrom string
}
// ToIni converts a profile to a struct with `ini` tags
// ready to be written to an ini config file.
//
// if noCredentialProcess is true, the struct will contain sso_ parameters
// like sso_role_name, sso_start_url, etc.
//
// if noCredentialProcess is false, the struct will contain granted_sso parameters
// for use with the Granted credential process, like granted_sso_role_name,
// granted_sso_start_url, and so forth.
func (p SSOProfile) ToIni(profileName string, noCredentialProcess bool) any {
if noCredentialProcess {
return ®ularProfile{
SSOStartURL: p.SSOStartURL,
SSORegion: p.SSORegion,
SSOAccountID: p.AccountID,
SSORoleName: p.RoleName,
CommonFateGeneratedFrom: p.GeneratedFrom,
Region: p.Region,
}
}
credProcess := "granted credential-process --profile " + profileName
if p.CommonFateURL != "" {
credProcess += " --url " + p.CommonFateURL
}
return &credentialProcessProfile{
SSOStartURL: p.SSOStartURL,
SSORegion: p.SSORegion,
SSOAccountID: p.AccountID,
SSORoleName: p.RoleName,
CredentialProcess: credProcess,
CommonFateGeneratedFrom: p.GeneratedFrom,
Region: p.Region,
}
}
type MergeOpts struct {
Config *ini.File
Prefix string
Profiles []SSOProfile
SectionNameTemplate string
NoCredentialProcess bool
// PruneStartURLs is a slice of AWS SSO start URLs which profiles are being generated for.
// Existing profiles with these start URLs will be removed if they aren't found in the Profiles field.
PruneStartURLs []string
}
func Merge(opts MergeOpts) error {
if opts.SectionNameTemplate == "" {
opts.SectionNameTemplate = "{{ .AccountName }}/{{ .RoleName }}"
}
// Sort profiles by CombinedName (AccountName/RoleName)
sort.SliceStable(opts.Profiles, func(i, j int) bool {
combinedNameI := opts.Profiles[i].AccountName + "/" + opts.Profiles[i].RoleName
combinedNameJ := opts.Profiles[j].AccountName + "/" + opts.Profiles[j].RoleName
return combinedNameI < combinedNameJ
})
funcMap := sprig.TxtFuncMap()
sectionNameTempl, err := template.New("").Funcs(funcMap).Parse(opts.SectionNameTemplate)
if err != nil {
return err
}
// remove any config sections that have 'common_fate_generated_from' as a key
for _, sec := range opts.Config.Sections() {
var startURL string
if sec.HasKey("granted_sso_start_url") {
startURL = sec.Key("granted_sso_start_url").String()
} else if sec.HasKey("sso_start_url") {
startURL = sec.Key("sso_start_url").String()
}
for _, pruneURL := range opts.PruneStartURLs {
isGenerated := sec.HasKey("common_fate_generated_from") // true if the profile was created automatically.
if isGenerated && startURL == pruneURL {
opts.Config.DeleteSection(sec.Name())
}
}
}
for _, ssoProfile := range opts.Profiles {
ssoProfile.AccountName = normalizeAccountName(ssoProfile.AccountName)
sectionNameBuffer := bytes.NewBufferString("")
err := sectionNameTempl.Execute(sectionNameBuffer, ssoProfile)
if err != nil {
return err
}
profileName := opts.Prefix + sectionNameBuffer.String()
sectionName := "profile " + profileName
opts.Config.DeleteSection(sectionName)
section, err := opts.Config.NewSection(sectionName)
if err != nil {
return err
}
entry := ssoProfile.ToIni(profileName, opts.NoCredentialProcess)
err = section.ReflectFrom(entry)
if err != nil {
return err
}
}
return nil
}
type credentialProcessProfile struct {
SSOStartURL string `ini:"granted_sso_start_url"`
SSORegion string `ini:"granted_sso_region"`
SSOAccountID string `ini:"granted_sso_account_id"`
SSORoleName string `ini:"granted_sso_role_name"`
CommonFateGeneratedFrom string `ini:"common_fate_generated_from"`
CredentialProcess string `ini:"credential_process"`
Region string `ini:"region,omitempty"`
}
type regularProfile struct {
SSOStartURL string `ini:"sso_start_url"`
SSORegion string `ini:"sso_region"`
SSOAccountID string `ini:"sso_account_id"`
CommonFateGeneratedFrom string `ini:"common_fate_generated_from"`
SSORoleName string `ini:"sso_role_name"`
Region string `ini:"region,omitempty"`
}
func normalizeAccountName(accountName string) string {
return strings.ReplaceAll(accountName, " ", "-")
}