Skip to content

Commit 900bd7a

Browse files
authored
Add profile templates (#32)
* add profile templates To hide some of the complexity of generating basic resources/permissions for the short-lived tokens, we are introducing a `profile-template` flag which will accept a value from the predefined list and generate the correct policies. Example to create a read-only short-lived token with a TTL of 15m. ``` cf-vault add my-example --profile-template "read-only" --session-duration "15m" ``` Closes #31
1 parent d902e6c commit 900bd7a

File tree

3 files changed

+195
-4
lines changed

3 files changed

+195
-4
lines changed

README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,21 @@ $ env | grep -i cloudflare
129129
# => no results
130130
```
131131

132+
## Predefined short lived token policies
133+
134+
If you don't need to generate a custom token policy, you can instead use one of
135+
the predefined templates which takes care of the heavy lifting for you. You can
136+
use `read-only` (read all resources) or `write-everything` (write all resources)
137+
as the `--profile-template` flag and it will generate everything needed behind
138+
the scenes on your behalf. Note: You **still** need to provide
139+
`--session-duration` as well otherwise the short lived tokens will not be
140+
generated.
141+
142+
Examples:
143+
144+
- `cf-vault add my-read-profile-name --profile-template "read-only" --session-duration "15m"`
145+
- `cf-vault add my-write-profile-name --profile-template "write-everything" --session-duration "15m"`
146+
132147
## Generating token policies
133148

134149
While TOML is more readable, its not always straight forward to generate the

cmd/add.go

Lines changed: 175 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"regexp"
1010
"strings"
1111

12+
"github.com/cloudflare/cloudflare-go"
1213
log "github.com/sirupsen/logrus"
1314
"golang.org/x/crypto/ssh/terminal"
1415

@@ -64,6 +65,8 @@ var addCmd = &cobra.Command{
6465
},
6566
Run: func(cmd *cobra.Command, args []string) {
6667
profileName := strings.TrimSpace(args[0])
68+
sessionDuration, _ := cmd.Flags().GetString("session-duration")
69+
profileTemplate, _ := cmd.Flags().GetString("profile-template")
6770

6871
reader := bufio.NewReader(os.Stdin)
6972
fmt.Print("Email address: ")
@@ -73,13 +76,14 @@ var addCmd = &cobra.Command{
7376
fmt.Print("Authentication value (API key or API token): ")
7477
byteAuthValue, err := terminal.ReadPassword(0)
7578
if err != nil {
76-
log.Fatalf("\nunable to read authentication value: %s", err)
79+
log.Fatal("unable to read authentication value: ", err)
7780
}
7881
authValue := string(byteAuthValue)
82+
fmt.Println()
7983

8084
authType, err := determineAuthType(strings.TrimSpace(authValue))
8185
if err != nil {
82-
log.Fatalf("failed to detect authentication type: %s", err)
86+
log.Fatal("failed to detect authentication type: ", err)
8387
}
8488

8589
home, err := homedir.Dir()
@@ -109,14 +113,55 @@ var addCmd = &cobra.Command{
109113
tomlConfigStruct.Profiles = make(map[string]profile)
110114
}
111115

112-
tomlConfigStruct.Profiles[profileName] = profile{
116+
newProfile := profile{
113117
Email: emailAddress,
114118
AuthType: authType,
115119
}
116120

121+
if sessionDuration != "" {
122+
newProfile.SessionDuration = sessionDuration
123+
} else {
124+
log.Debug("session-duration was not set, not using short lived tokens")
125+
}
126+
127+
var api *cloudflare.API
128+
if authType == "api_token" {
129+
api, err = cloudflare.NewWithAPIToken(authValue)
130+
if err != nil {
131+
log.Fatal(err)
132+
}
133+
} else {
134+
api, err = cloudflare.New(authValue, emailAddress)
135+
if err != nil {
136+
log.Fatal(err)
137+
}
138+
}
139+
140+
if profileTemplate != "" {
141+
// The policies require that one of the resources is the current user.
142+
// This leads to a potential chicken/egg scenario where the user doesn't
143+
// valid credentials but needs them to generate the resources. We
144+
// intentionally spit out `Debug` and `Fatal` messages here to show the
145+
// original error *and* the friendly version of how to resolve it.
146+
userDetails, err := api.UserDetails()
147+
if err != nil {
148+
log.Debug(err)
149+
log.Fatal("failed to fetch user ID from the Cloudflare API which is required to generate the predefined short lived token policies. If you are using API tokens, please allow the permission to access your user details and try again.")
150+
}
151+
152+
generatedPolicy, err := generatePolicy(profileTemplate, userDetails.ID)
153+
if err != nil {
154+
log.Fatal(err)
155+
}
156+
newProfile.Policies = generatedPolicy
157+
}
158+
159+
log.Debugf("new profile: %+v", newProfile)
160+
tomlConfigStruct.Profiles[profileName] = newProfile
161+
117162
configFile, err := os.OpenFile(home+defaultFullConfigPath, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0700)
118163
if err != nil {
119-
log.Fatalf("failed to open file at %s", home+defaultFullConfigPath)
164+
log.Fatal("failed to open file at ", home+defaultFullConfigPath)
120165
}
121166
defer configFile.Close()
122167
if err := toml.NewEncoder(configFile).Encode(tomlConfigStruct); err != nil {
@@ -145,3 +190,129 @@ func determineAuthType(s string) (string, error) {
145190
return "", errors.New("invalid API token or API key format")
146191
}
147192
}
193+
194+
func generatePolicy(policyType, userID string) ([]policy, error) {
195+
readOnlyPolicy := []policy{
196+
{
197+
Effect: "allow",
198+
Resources: map[string]interface{}{"com.cloudflare.api.account.*": "*"},
199+
PermissionGroups: []permissionGroup{
200+
{ID: "7ea222f6d5064cfa89ea366d7c1fee89"},
201+
{ID: "b05b28e839c54467a7d6cba5d3abb5a3"},
202+
{ID: "4f3196a5c95747b6ad82e34e1d0a694f"},
203+
{ID: "0f4841f80adb4bada5a09493300e7f8d"},
204+
{ID: "26bc23f853634eb4bff59983b9064fde"},
205+
{ID: "91f7ce32fa614d73b7e1fc8f0e78582b"},
206+
{ID: "b89a480218d04ceb98b4fe57ca29dc1f"},
207+
{ID: "de7a688cc47d43bd9ea700b467a09c96"},
208+
{ID: "4f1071168de8466e9808de86febfc516"},
209+
{ID: "c1fde68c7bcc44588cbb6ddbc16d6480"},
210+
{ID: "efea2ab8357b47888938f101ae5e053f"},
211+
{ID: "7cf72faf220841aabcfdfab81c43c4f6"},
212+
{ID: "5f48a472240a4b489a21d43bd19a06e1"},
213+
{ID: "e763fae6ee95443b8f56f19213c5f2a5"},
214+
{ID: "9d24387c6e8544e2bc4024a03991339f"},
215+
{ID: "6a315a56f18441e59ed03352369ae956"},
216+
{ID: "58abbad6d2ce40abb2594fbe932a2e0e"},
217+
{ID: "de21485a24744b76a004aa153898f7fe"},
218+
{ID: "3f376c8e6f764a938b848bd01c8995c4"},
219+
{ID: "8b47d2786a534c08a1f94ee8f9f599ef"},
220+
{ID: "1a71c399035b4950a1bd1466bbe4f420"},
221+
{ID: "05880cd1bdc24d8bae0be2136972816b"},
222+
},
223+
},
224+
{
225+
Effect: "allow",
226+
Resources: map[string]interface{}{"com.cloudflare.api.account.zone.*": "*"},
227+
PermissionGroups: []permissionGroup{
228+
{ID: "eb258a38ea634c86a0c89da6b27cb6b6"},
229+
{ID: "9c88f9c5bce24ce7af9a958ba9c504db"},
230+
{ID: "82e64a83756745bbbb1c9c2701bf816b"},
231+
{ID: "4ec32dfcb35641c5bb32d5ef1ab963b4"},
232+
{ID: "e9a975f628014f1d85b723993116f7d5"},
233+
{ID: "c4a30cd58c5d42619c86a3c36c441e2d"},
234+
{ID: "b415b70a4fd1412886f164451f20405c"},
235+
{ID: "7b7216b327b04b8fbc8f524e1f9b7531"},
236+
{ID: "2072033d694d415a936eaeb94e6405b8"},
237+
{ID: "c8fed203ed3043cba015a93ad1616f1f"},
238+
{ID: "517b21aee92c4d89936c976ba6e4be55"},
239+
},
240+
},
241+
{
242+
Effect: "allow",
243+
Resources: map[string]interface{}{"com.cloudflare.api.user." + userID: "*"},
244+
PermissionGroups: []permissionGroup{
245+
{ID: "3518d0f75557482e952c6762d3e64903"},
246+
{ID: "8acbe5bb0d54464ab867149d7f7cf8ac"},
247+
},
248+
},
249+
}
250+
251+
writeEverythingPolicy := []policy{
252+
{
253+
Effect: "allow",
254+
Resources: map[string]interface{}{"com.cloudflare.api.account.*": "*"},
255+
PermissionGroups: []permissionGroup{
256+
{ID: "1e13c5124ca64b72b1969a67e8829049"},
257+
{ID: "b05b28e839c54467a7d6cba5d3abb5a3"},
258+
{ID: "29d3afbfd4054af9accdd1118815ed05"},
259+
{ID: "2fc1072ee6b743828db668fcb3f9dee7"},
260+
{ID: "bfe0d8686a584fa680f4c53b5eb0de6d"},
261+
{ID: "a1c0fec57cf94af79479a6d827fa518c"},
262+
{ID: "b89a480218d04ceb98b4fe57ca29dc1f"},
263+
{ID: "a416acf9ef5a4af19fb11ed3b96b1fe6"},
264+
{ID: "2edbf20661fd4661b0fe10e9e12f485c"},
265+
{ID: "1af1fa2adc104452b74a9a3364202f20"},
266+
{ID: "c07321b023e944ff818fec44d8203567"},
267+
{ID: "6c80e02421494afc9ae14414ed442632"},
268+
{ID: "da6d2d6f2ec8442eaadda60d13f42bca"},
269+
{ID: "2ae23e4939d54074b7d252d27ce75a77"},
270+
{ID: "d2a1802cc9a34e30852f8b33869b2f3c"},
271+
{ID: "96163bd1b0784f62b3e44ed8c2ab1eb6"},
272+
{ID: "61ddc58f1da14f95b33b41213360cbeb"},
273+
{ID: "b33f02c6f7284e05a6f20741c0bb0567"},
274+
{ID: "f7f0eda5697f475c90846e879bab8666"},
275+
{ID: "e086da7e2179491d91ee5f35b3ca210a"},
276+
{ID: "05880cd1bdc24d8bae0be2136972816b"},
277+
},
278+
},
279+
{
280+
Effect: "allow",
281+
Resources: map[string]interface{}{"com.cloudflare.api.account.zone.*": "*"},
282+
PermissionGroups: []permissionGroup{
283+
{ID: "959972745952452f8be2452be8cbb9f2"},
284+
{ID: "9c88f9c5bce24ce7af9a958ba9c504db"},
285+
{ID: "094547ab6e77498c8c4dfa87fadd5c51"},
286+
{ID: "e17beae8b8cb423a99b1730f21238bed"},
287+
{ID: "4755a26eedb94da69e1066d98aa820be"},
288+
{ID: "43137f8d07884d3198dc0ee77ca6e79b"},
289+
{ID: "6d7f2f5f5b1d4a0e9081fdc98d432fd1"},
290+
{ID: "3e0b5820118e47f3922f7c989e673882"},
291+
{ID: "ed07f6c337da4195b4e72a1fb2c6bcae"},
292+
{ID: "c03055bc037c4ea9afb9a9f104b7b721"},
293+
{ID: "28f4b596e7d643029c524985477ae49a"},
294+
{ID: "e6d2666161e84845a636613608cee8d5"},
295+
{ID: "3030687196b94b638145a3953da2b699"},
296+
},
297+
},
298+
{
299+
Effect: "allow",
300+
Resources: map[string]interface{}{"com.cloudflare.api.user." + userID: "*"},
301+
PermissionGroups: []permissionGroup{
302+
{ID: "9201bc6f42d440968aaab0c6f17ebb1d"},
303+
{ID: "55a5e17cc99e4a3fa1f3432d262f2e55"},
304+
},
305+
},
306+
}
307+
308+
switch policyType {
309+
case "write-everything":
310+
log.Debug("configuring a write-everything template")
311+
return writeEverythingPolicy, nil
312+
case "read-only":
313+
log.Debug("configuring a read-only template")
314+
return readOnlyPolicy, nil
315+
}
316+
317+
return nil, fmt.Errorf("unable to generate policy for %q", policyType)
318+
}

cmd/root.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,11 @@ func init() {
4949

5050
rootCmd.PersistentFlags().BoolVarP(&verbose, "verbose", "v", false, "increase the verbosity of the output")
5151

52+
var profileTemplate string
53+
var sessionDuration string
54+
addCmd.Flags().StringVarP(&profileTemplate, "profile-template", "", "", "create profile with a predefined permissions and resources template")
55+
addCmd.Flags().StringVarP(&sessionDuration, "session-duration", "", "", "TTL of short lived tokens requests")
56+
5257
rootCmd.AddCommand(addCmd)
5358
rootCmd.AddCommand(listCmd)
5459
rootCmd.AddCommand(execCmd)

0 commit comments

Comments
 (0)