Skip to content

Commit 9a8c67a

Browse files
committed
Add profile flag
1 parent d475cae commit 9a8c67a

File tree

2 files changed

+99
-98
lines changed

2 files changed

+99
-98
lines changed

tools/ghsecrets/README.md

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,43 +27,43 @@ By default, `ghsecrets set` assumes you want to store secrets in AWS Secrets Man
2727

2828
#### a) Set secrets in AWS (default)
2929

30-
This will read from `~/.testsecrets` (by default) and create/update a secret in AWS Secrets Manager:
31-
3230
> **⚠️ Note:** Ensure you authenticate with AWS before using the tool:
3331
>
3432
> ```sh
35-
> aws sso login --profile <my-profile>
33+
> aws sso login --profile <your-aws-profile>
3634
> ```
37-
> By default, use the `SDLC` profile
35+
> By default, use the SDLC profile
36+
37+
This will read from `~/.testsecrets` (by default) and create/update a secret in AWS Secrets Manager:
3838
3939
```sh
40-
ghsecrets set
40+
ghsecrets set --profile <your-aws-profile>
4141
```
4242
4343
If you’d like to specify a different file:
4444

4545
```sh
46-
ghsecrets set --file /path/to/mysecrets.env
46+
ghsecrets set --file /path/to/mysecrets.env --profile <your-aws-profile>
4747
```
4848

4949
If you’d like to specify a custom secret name:
5050

5151
```sh
52-
ghsecrets set --secret-id my-custom-secret
52+
ghsecrets set --secret-id my-custom-secret --profile <your-aws-profile>
5353
```
5454

5555
Note: For AWS backend, the tool automatically adds the `testsecrets/` prefix if it is missing. This ensures consistency and allows GitHub Actions to access all secrets with this designated prefix.
5656

5757
If you’d like to share this secret with additional AWS IAM principals (e.g., a collaborator’s account):
5858

5959
```sh
60-
ghsecrets set --shared-with arn:aws:iam::123456789012:role/SomeRole
60+
ghsecrets set --shared-with arn:aws:iam::123456789012:role/SomeRole --profile <your-aws-profile>
6161
```
6262

6363
You can specify multiple ARNs using commas:
6464

6565
```sh
66-
ghsecrets set --shared-with arn:aws:iam::123456789012:role/SomeRole,arn:aws:iam::345678901234:root
66+
ghsecrets set --shared-with arn:aws:iam::123456789012:role/SomeRole,arn:aws:iam::345678901234:root --profile <your-aws-profile>
6767
```
6868

6969
#### b) Set secrets in GitHub
@@ -82,13 +82,13 @@ This will:
8282
If you want to retrieve an existing secret from AWS Secrets Manager, use:
8383

8484
```sh
85-
ghsecrets get --secret-id testsecrets/MySecretName
85+
ghsecrets get --secret-id testsecrets/MySecretName --profile <your-aws-profile>
8686
```
8787

8888
By default, it prints out the Base64-encoded string. To decode it automatically:
8989

9090
```sh
91-
ghsecrets get --secret-id testsecrets/MySecretName --decode
91+
ghsecrets get --secret-id testsecrets/MySecretName --decode --profile <your-aws-profile>
9292
```
9393

9494
## FAQ

tools/ghsecrets/main.go

Lines changed: 88 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ func main() {
2525
var secretID string
2626
var backend string // Backend: GitHub or AWS
2727
var decode bool // Decode flag for `get`
28+
var profile string // AWS profile to use
2829
var sharedWith []string // List of ARNs to share the secret with
2930

3031
// Set Command
@@ -57,10 +58,13 @@ func main() {
5758
return
5859
}
5960
case "aws":
61+
if profile == "" {
62+
exitWithError(nil, "AWS profile is required when using the AWS backend. Use the --profile flag to specify it.")
63+
return
64+
}
6065
// Ensure AWS secretID starts with "testsecrets/" prefix
61-
// GHA IAM role has a policy that restricts access to secrets with this prefix
6266
secretID = ensurePrefix(secretID, "testsecrets/")
63-
if err := setAWSSecret(filePath, secretID, sharedWith); err != nil {
67+
if err := setAWSSecret(filePath, secretID, profile, sharedWith); err != nil {
6468
exitWithError(err, "Failed to set AWS secret")
6569
return
6670
}
@@ -76,8 +80,12 @@ func main() {
7680
Use: "get",
7781
Short: "Retrieve a secret from AWS Secrets Manager",
7882
Run: func(cmd *cobra.Command, args []string) {
83+
if profile == "" {
84+
exitWithError(nil, "AWS profile is required when using the AWS backend. Use the --profile flag to specify it.")
85+
return
86+
}
7987
secretID = ensurePrefix(secretID, "testsecrets/")
80-
if err := getAWSSecret(secretID, decode); err != nil {
88+
if err := getAWSSecret(secretID, decode, profile); err != nil {
8189
exitWithError(err, "Failed to retrieve AWS secret")
8290
}
8391
},
@@ -94,12 +102,13 @@ func main() {
94102
setCmd.PersistentFlags().StringVarP(&filePath, "file", "f", defaultSecretsPath(), "Path to file with test secrets")
95103
setCmd.PersistentFlags().StringVarP(&secretID, "secret-id", "s", "", "ID of the secret to set")
96104
setCmd.PersistentFlags().StringVarP(&backend, "backend", "b", "aws", "Backend to use for storing secrets. Options: github, aws")
105+
setCmd.PersistentFlags().StringVar(&profile, "profile", "", "AWS profile to use for credentials (required for AWS backend)")
97106
setCmd.PersistentFlags().StringSliceVar(&sharedWith, "shared-with", []string{}, "Comma-separated list of IAM ARNs to share the secret with")
98107

99108
getCmd.PersistentFlags().StringVarP(&secretID, "secret-id", "s", "", "ID of the secret to retrieve")
100109
getCmd.PersistentFlags().BoolVarP(&decode, "decode", "d", false, "Decode the Base64-encoded secret value")
110+
getCmd.PersistentFlags().StringVar(&profile, "profile", "", "AWS profile to use for credentials (required for AWS backend)")
101111

102-
// Make secretID a required flag for the set command
103112
getCmd.MarkPersistentFlagRequired("secret-id")
104113

105114
if err := rootCmd.Execute(); err != nil {
@@ -114,10 +123,8 @@ func setGitHubSecret(filePath, secretID string) error {
114123
return fmt.Errorf("failed to read file: %w", err)
115124
}
116125

117-
// Base64 encode the file content
118126
encoded := base64.StdEncoding.EncodeToString(data)
119127

120-
// Construct the GitHub CLI command to set the secret
121128
setSecretCmd := exec.Command("gh", "secret", "set", secretID, "--body", encoded)
122129
setSecretCmd.Stdin = strings.NewReader(encoded)
123130

@@ -136,18 +143,16 @@ func setGitHubSecret(filePath, secretID string) error {
136143
}
137144

138145
// setAWSSecret creates or updates a secret in AWS Secrets Manager
139-
func setAWSSecret(filePath, secretID string, sharedWith []string) error {
140-
secretID = ensurePrefix(secretID, "testsecrets/") // Ensure prefix
141-
146+
func setAWSSecret(filePath, secretID, profile string, sharedWith []string) error {
142147
data, err := os.ReadFile(filePath)
143148
if err != nil {
144149
return fmt.Errorf("failed to read file: %w", err)
145150
}
146151
encoded := base64.StdEncoding.EncodeToString(data)
147152

148-
cfg, err := config.LoadDefaultConfig(context.TODO())
153+
cfg, err := loadAWSConfig(profile)
149154
if err != nil {
150-
return fmt.Errorf("failed to load AWS config: %w", err)
155+
return handleAWSSSOError(err)
151156
}
152157

153158
smClient := secretsmanager.NewFromConfig(cfg)
@@ -167,33 +172,20 @@ func setAWSSecret(filePath, secretID string, sharedWith []string) error {
167172
Description: aws.String("Secret updated by ghsecrets CLI"),
168173
})
169174
if err != nil {
170-
if strings.Contains(err.Error(), "InvalidGrantException") {
171-
return fmt.Errorf(
172-
"Your AWS SSO session has likely expired. Please re-authenticate by running:\n\n aws sso login --profile <my-profile>\n\nThen try again.\n\nOriginal error: %w",
173-
err,
174-
)
175-
}
176-
return fmt.Errorf("failed to update AWS secret: %w", err)
175+
return handleAWSSSOError(err)
177176
}
178177
} else {
179-
if strings.Contains(err.Error(), "InvalidGrantException") {
180-
return fmt.Errorf(
181-
"Your AWS SSO session has likely expired. Please re-authenticate by running:\n\n aws sso login --profile <my-profile>\n\nThen try again.\n\nOriginal error: %w",
182-
err,
183-
)
184-
}
185-
return fmt.Errorf("failed to create AWS secret: %w", err)
178+
return handleAWSSSOError(err)
186179
}
187180
}
188181

189182
if len(sharedWith) > 0 {
190-
err = updateAWSSecretAccessPolicy(secretID, sharedWith)
183+
err = updateAWSSecretAccessPolicy(secretID, sharedWith, profile)
191184
if err != nil {
192185
return fmt.Errorf("failed to update secret sharing policy: %w", err)
193186
}
194187
}
195188

196-
// Success message with AWS-specific instructions
197189
fmt.Printf(
198190
"Test secret set successfully in AWS Secrets Manager with key: %s\n\n"+
199191
"To use this secret in a GitHub workflow, set the 'test_secrets_override_key' flag with the 'aws:' prefix. Example:\n"+
@@ -203,10 +195,38 @@ func setAWSSecret(filePath, secretID string, sharedWith []string) error {
203195
return nil
204196
}
205197

198+
// getAWSSecret retrieves a test secret from AWS Secrets Manager
199+
func getAWSSecret(secretID string, decode bool, profile string) error {
200+
cfg, err := loadAWSConfig(profile)
201+
if err != nil {
202+
return handleAWSSSOError(err)
203+
}
204+
205+
smClient := secretsmanager.NewFromConfig(cfg)
206+
out, err := smClient.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{
207+
SecretId: aws.String(secretID),
208+
})
209+
if err != nil {
210+
return handleAWSSSOError(err)
211+
}
212+
213+
value := aws.ToString(out.SecretString)
214+
if decode {
215+
decoded, err := base64.StdEncoding.DecodeString(value)
216+
if err != nil {
217+
return fmt.Errorf("failed to decode secret value: %w", err)
218+
}
219+
value = string(decoded)
220+
}
221+
222+
fmt.Printf("Retrieved secret value:\n%s\n", value)
223+
return nil
224+
}
225+
206226
// updateAWSSecretAccessPolicy updates the sharing policy for a secret in AWS Secrets Manager
207-
func updateAWSSecretAccessPolicy(secretID string, sharedWith []string) error {
227+
func updateAWSSecretAccessPolicy(secretID string, sharedWith []string, profile string) error {
208228
// 1) Load AWS config
209-
cfg, err := config.LoadDefaultConfig(context.TODO())
229+
cfg, err := loadAWSConfig(profile)
210230
if err != nil {
211231
return fmt.Errorf("failed to load AWS config: %w", err)
212232
}
@@ -262,56 +282,19 @@ func updateAWSSecretAccessPolicy(secretID string, sharedWith []string) error {
262282
return nil
263283
}
264284

265-
// getAWSSecret retrieves a test secret from AWS Secrets Manager
266-
// getAWSSecret retrieves a test secret from AWS Secrets Manager
267-
func getAWSSecret(secretID string, decode bool) error {
268-
cfg, err := config.LoadDefaultConfig(context.TODO())
269-
if err != nil {
270-
return fmt.Errorf("failed to load AWS config: %w", err)
271-
}
272-
273-
smClient := secretsmanager.NewFromConfig(cfg)
274-
out, err := smClient.GetSecretValue(context.TODO(), &secretsmanager.GetSecretValueInput{
275-
SecretId: aws.String(secretID),
276-
})
277-
if err != nil {
278-
// Check if the error is due to an expired SSO token
279-
if strings.Contains(err.Error(), "InvalidGrantException") {
280-
return fmt.Errorf(
281-
"Your AWS SSO session has likely expired. Please re-authenticate by running:\n\n aws sso login --profile <my-profile>\n\nThen try again.\n\nOriginal error: %w",
282-
err,
283-
)
284-
}
285-
return fmt.Errorf("failed to retrieve AWS secret: %w", err)
286-
}
287-
288-
value := aws.ToString(out.SecretString)
289-
if decode {
290-
decoded, err := base64.StdEncoding.DecodeString(value)
291-
if err != nil {
292-
return fmt.Errorf("failed to decode secret value: %w", err)
293-
}
294-
value = string(decoded)
295-
}
296-
297-
fmt.Printf("Retrieved secret value:\n%s\n", value)
298-
return nil
299-
}
300-
301-
// =======================================================================
302285
// Utility Functions
303-
// =======================================================================
304-
func defaultSecretsPath() string {
305-
homeDir, err := os.UserHomeDir()
306-
if err != nil {
307-
log.Fatalf("Failed to get user home directory: %s", err)
308-
}
309-
return filepath.Join(homeDir, ".testsecrets")
286+
func loadAWSConfig(profile string) (aws.Config, error) {
287+
return config.LoadDefaultConfig(
288+
context.TODO(),
289+
config.WithSharedConfigProfile(profile),
290+
)
310291
}
311292

312-
func isGHInstalled() bool {
313-
_, err := exec.LookPath("gh")
314-
return err == nil
293+
func ensurePrefix(secretID, prefix string) string {
294+
if !strings.HasPrefix(secretID, prefix) {
295+
return prefix + secretID
296+
}
297+
return secretID
315298
}
316299

317300
func validateFile(filePath string) error {
@@ -325,6 +308,27 @@ func validateFile(filePath string) error {
325308
return nil
326309
}
327310

311+
func handleAWSSSOError(err error) error {
312+
if strings.Contains(err.Error(), "SSO session has expired") || strings.Contains(err.Error(), "InvalidGrantException") {
313+
return fmt.Errorf(
314+
"AWS SSO session has expired or is invalid. Please re-authenticate by running:\n\n"+
315+
" aws sso login --profile <your-profile>\n\n"+
316+
"Then try again with --profile <your-profile> flag.\n\nOriginal error: %w",
317+
err,
318+
)
319+
}
320+
return fmt.Errorf("AWS operation failed: %w", err)
321+
}
322+
323+
func exitWithError(err error, msg string) {
324+
if err != nil {
325+
fmt.Fprintf(os.Stderr, "%s: %v\n", msg, err)
326+
} else {
327+
fmt.Fprintf(os.Stderr, "%s\n", msg)
328+
}
329+
os.Exit(1)
330+
}
331+
328332
func generateSecretIDFromGithubUsername() (string, error) {
329333
usernameCmd := exec.Command("gh", "api", "user", "--jq", ".login")
330334
usernameOutput, err := usernameCmd.CombinedOutput()
@@ -336,18 +340,15 @@ func generateSecretIDFromGithubUsername() (string, error) {
336340
return strings.ToUpper(secretID), nil
337341
}
338342

339-
func ensurePrefix(secretID, prefix string) string {
340-
if !strings.HasPrefix(secretID, prefix) {
341-
return prefix + secretID
343+
func defaultSecretsPath() string {
344+
homeDir, err := os.UserHomeDir()
345+
if err != nil {
346+
log.Fatalf("Failed to get user home directory: %s", err)
342347
}
343-
return secretID
348+
return filepath.Join(homeDir, ".testsecrets")
344349
}
345350

346-
func exitWithError(err error, msg string) {
347-
if err != nil {
348-
fmt.Fprintf(os.Stderr, "%s: %v\n", msg, err)
349-
} else {
350-
fmt.Fprintf(os.Stderr, "%s\n", msg)
351-
}
352-
os.Exit(1)
351+
func isGHInstalled() bool {
352+
_, err := exec.LookPath("gh")
353+
return err == nil
353354
}

0 commit comments

Comments
 (0)