Skip to content

Commit 4f46994

Browse files
lexbritvindavidferlay
authored andcommitted
Use sensitive output.
1 parent 8d49067 commit 4f46994

File tree

10 files changed

+136
-11
lines changed

10 files changed

+136
-11
lines changed

.github/workflows/commit.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ jobs:
2222
make deps build
2323
ls -lah bin/launchr
2424
./bin/launchr keyring:login --url=http://***.git --username="***" --password="***" --keyring-passphrase="***"
25+
echo "***" > my_secret
26+
./bin/launchr keyring:login --url=http://***.git --username="***" --password="***" --keyring-passphrase-file="my_secret"
27+
28+
LAUNCHR_KEYRING_PASSPHRASE=mypassphrase ./bin/launchr example:shell
29+
echo "mypassphrase" > my_secret
30+
LAUNCHR_KEYRING_PASSPHRASE_FILE=my_secret ./bin/launchr example:shell
2531
2632
commands-ok:
2733
name: Ensure main commands do not fail
@@ -39,9 +45,9 @@ jobs:
3945
whoami
4046
make deps build
4147
ls -lah bin/launchr
42-
./bin/launchr keyring:set vaultpass "***" --keyring-passphrase "***"
48+
./bin/launchr keyring:set vaultpass "myvaultpass" --keyring-passphrase "mypassphrase"
4349
ls -lah .launchr/keyring.yaml.age
44-
./bin/launchr keyring:unset vaultpass --keyring-passphrase "***"
50+
./bin/launchr keyring:unset vaultpass --keyring-passphrase "mypassphrase"
4551
ls -lah .launchr/keyring.yaml.age
4652
4753
go-linters:

README.md

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,45 @@ launchr login
1212

1313
If an interactive shell is not available, credentials may be provided with flags:
1414
```shell
15+
# Input passphrase directly
1516
launchr login \
1617
--url=https://your.gitlab.com \
1718
--username=USER \
1819
--password=SECRETPASSWORD \
1920
--keyring-passphrase=YOURPASSHRPASE
21+
22+
# Get passphrase from a file
23+
launchr login \
24+
--url=https://your.gitlab.com \
25+
--username=USER \
26+
--password=SECRETPASSWORD \
27+
--keyring-passphrase-file=/path/to/your/secret
2028
```
2129

22-
Flag `--keyring-passphrase` is available for all launchr commands, for example:
30+
Flags `--keyring-passphrase` and `--keyring-passphrase-file` are available for all launchr commands, for example:
2331
```shell
2432
launchr compose --keyring-passphrase=YOURPASSHRPASE
33+
launchr compose --keyring-passphrase-file=/path/to/your/secret
2534
```
2635

36+
These flags may be passed as environment variables `LAUNCHR_KEYRING_PASSPHRASE` and `LAUNCHR_KEYRING_PASSPHRASE_FILE`:
37+
```shell
38+
LAUNCHR_KEYRING_PASSPHRASE=YOURPASSHRPASE launchr compose
39+
LAUNCHR_KEYRING_PASSPHRASE_FILE=/path/to/your/secret launchr compose
40+
```
41+
42+
Flags and environment variables are taken in the following priority:
43+
1. `--keyring-passphrase`
44+
2. `LAUNCHR_KEYRING_PASSPHRASE`
45+
3. `--keyring-passphrase-file`
46+
4. `LAUNCHR_KEYRING_PASSPHRASE_FILE`
47+
48+
**NB:** If the binary is created with a specific app name like `myappname`, the variable name will change accordingly `MYAPPNAME_KEYRING_PASSPHRASE_FILE`.
49+
50+
Flag `--keyring-passphrase-file` will also set `LAUNCHR_KEYRING_PASSPHRASE_FILE` for subprocesses.
51+
These environment variables are inherited in subprocesses.
52+
Using `--keyring-passphrase-file` or `LAUNCHR_KEYRING_PASSPHRASE_FILE` is a preferred way to pass the secret because the secret won't be exposed.
53+
2754
To delete an item from the keyring:
2855
```shell
2956
launchr logout URL

example/actions/shell/action.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
action:
2+
title: test env keyring
3+
runtime:
4+
type: shell
5+
script: |
6+
echo "Main env:"
7+
env | grep "LAUNCHR"
8+
{{ .current_bin }} keyring:purge
9+
{{ .current_bin }} keyring:set storedsecret "my_secret"
10+
echo "Subprocess env:"
11+
{{ .current_bin }} example:subshell
12+
{{ .current_bin }} keyring:purge
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
action:
2+
title: test env keyring
3+
options:
4+
- name: secret
5+
process:
6+
- processor: keyring.GetKeyValue
7+
options:
8+
key: storedsecret
9+
runtime:
10+
type: shell
11+
script: |
12+
env | grep "LAUNCHR"
13+
echo "My secret from keyring: {{ .secret }}"

file.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"filippo.io/age"
10+
1011
"github.com/launchrctl/launchr"
1112
)
1213

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ toolchain go1.23.4
66

77
require (
88
filippo.io/age v1.2.1
9-
github.com/launchrctl/launchr v0.18.0
9+
github.com/launchrctl/launchr v0.18.3-0.20250316225153-b280479eabd0
1010
github.com/stretchr/testify v1.10.0
1111
golang.org/x/term v0.29.0
1212
gopkg.in/yaml.v3 v3.0.1

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
230230
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
231231
github.com/launchrctl/launchr v0.18.0 h1:Kp5iguwl5rx1qo2M3A2eMlhTeefgL5UDKFzP/7B45UE=
232232
github.com/launchrctl/launchr v0.18.0/go.mod h1:hhJSGcxn1FhD267u0JfNu6u65naTQAVxElnqUTY9nag=
233+
github.com/launchrctl/launchr v0.18.3-0.20250316225153-b280479eabd0 h1:cOlcoOCphR/qR0Fw2/W8xjbFHF0kuYbRnIZ6M8CXosI=
234+
github.com/launchrctl/launchr v0.18.3-0.20250316225153-b280479eabd0/go.mod h1:hhJSGcxn1FhD267u0JfNu6u65naTQAVxElnqUTY9nag=
233235
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
234236
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
235237
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=

keyring.go

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,12 +87,14 @@ type keyringService struct {
8787
fname string
8888
store DataStore
8989
cfg launchr.Config
90+
mask *launchr.SensitiveMask
9091
}
9192

92-
func newKeyringService(cfg launchr.Config) Keyring {
93+
func newKeyringService(cfg launchr.Config, mask *launchr.SensitiveMask) Keyring {
9394
return &keyringService{
9495
fname: cfg.Path(defaultFileYaml),
9596
cfg: cfg,
97+
mask: mask,
9698
}
9799
}
98100

@@ -128,7 +130,11 @@ func (k *keyringService) GetForURL(url string) (CredentialsItem, error) {
128130
if err != nil {
129131
return CredentialsItem{}, err
130132
}
131-
return s.GetForURL(url)
133+
item, err := s.GetForURL(url)
134+
if err == nil {
135+
k.mask.AddString(item.Password)
136+
}
137+
return item, err
132138
}
133139

134140
// GetForKey implements DataStore interface. Uses service default store.
@@ -137,7 +143,11 @@ func (k *keyringService) GetForKey(key string) (KeyValueItem, error) {
137143
if err != nil {
138144
return KeyValueItem{}, err
139145
}
140-
return s.GetForKey(key)
146+
item, err := s.GetForKey(key)
147+
if err == nil {
148+
k.mask.AddString(item.Value)
149+
}
150+
return item, err
141151
}
142152

143153
// AddItem implements DataStore interface. Uses service default store.

plugin.go

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,15 @@ const (
2121
procGetKeyValue = "keyring.GetKeyValue"
2222
errTplNotFoundURL = "%s not found in keyring. Use `%s keyring:login` to add it."
2323
errTplNotFoundKey = "%s not found in keyring. Use `%s keyring:set` to add it."
24+
25+
envVarPassphrase = launchr.EnvVar("keyring_passphrase")
26+
envVarPassphraseFile = launchr.EnvVar("keyring_passphrase_file")
2427
)
2528

26-
var passphrase string
29+
var (
30+
passphrase string
31+
passphraseFile string
32+
)
2733

2834
var (
2935
//go:embed action.login.yaml
@@ -44,8 +50,9 @@ func init() {
4450

4551
// Plugin is [launchr.Plugin] plugin providing a keyring.
4652
type Plugin struct {
47-
k Keyring
48-
cfg launchr.Config
53+
k Keyring
54+
cfg launchr.Config
55+
mask *launchr.SensitiveMask
4956
}
5057

5158
// PluginInfo implements [launchr.Plugin] interface.
@@ -55,8 +62,9 @@ func (p *Plugin) PluginInfo() launchr.PluginInfo {
5562

5663
// OnAppInit implements [launchr.Plugin] interface.
5764
func (p *Plugin) OnAppInit(app launchr.App) error {
65+
p.mask = app.SensitiveMask()
5866
app.GetService(&p.cfg)
59-
p.k = newKeyringService(p.cfg)
67+
p.k = newKeyringService(p.cfg, p.mask)
6068
app.AddService(p.k)
6169

6270
var m action.Manager
@@ -164,6 +172,20 @@ func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) {
164172
// CobraAddCommands implements [launchr.CobraPlugin] interface to provide keyring functionality.
165173
func (p *Plugin) CobraAddCommands(rootCmd *launchr.Command) error {
166174
rootCmd.PersistentFlags().StringVarP(&passphrase, "keyring-passphrase", "", "", "Passphrase for keyring encryption/decryption")
175+
rootCmd.PersistentFlags().StringVarP(&passphraseFile, "keyring-passphrase-file", "", "", "File containing passphrase for keyring encryption/decryption")
176+
return nil
177+
}
178+
179+
// PersistentPreRun implements [launchr.PersistentPreRun] interface.
180+
func (p *Plugin) PersistentPreRun(_ *launchr.Command, _ []string) error {
181+
setPassphrase()
182+
// If the passphrase is set with env variables, hide them.
183+
if passphraseFile == "" && passphrase != "" {
184+
p.mask.AddString(passphrase)
185+
}
186+
if passphraseFile != "" {
187+
p.mask.AddString(passphraseFile)
188+
}
167189
return nil
168190
}
169191

@@ -310,3 +332,33 @@ func removeKey(k Keyring, key string, all bool) error {
310332
func purge(k Keyring) error {
311333
return k.Destroy()
312334
}
335+
336+
func setPassphrase() {
337+
// Return passphrase if it's already provided.
338+
if passphrase != "" {
339+
return
340+
}
341+
// Check env variable for the passphrase.
342+
passphrase = envVarPassphrase.Get()
343+
if passphrase != "" {
344+
return
345+
}
346+
if envPassFile := envVarPassphraseFile.Get(); passphraseFile == "" && envPassFile != "" {
347+
passphraseFile = launchr.MustAbs(envPassFile)
348+
// Override to absolute path.
349+
_ = envVarPassphraseFile.Set(passphraseFile)
350+
}
351+
352+
// Try to read a secret from a file.
353+
if passphraseFile != "" {
354+
passphraseFile = launchr.MustAbs(passphraseFile)
355+
bytes, err := os.ReadFile(passphraseFile) //nolint:gosec // Filepath checked on previous line.
356+
if err != nil {
357+
return
358+
}
359+
passphrase = strings.TrimSpace(string(bytes))
360+
// Set env variable for subprocesses.
361+
_ = envVarPassphraseFile.Set(passphraseFile)
362+
return
363+
}
364+
}

plugin_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package keyring
33
import (
44
"testing"
55

6+
"github.com/launchrctl/launchr"
67
"github.com/stretchr/testify/require"
78

89
"github.com/launchrctl/launchr/pkg/action"
@@ -60,6 +61,7 @@ func Test_KeyringProcessor(t *testing.T) {
6061
// Prepare services.
6162
k := &keyringService{
6263
store: &dataStoreYaml{file: &plainFile{fname: "teststorage.yaml"}},
64+
mask: &launchr.SensitiveMask{},
6365
}
6466
am := action.NewManager()
6567
addValueProcessors(am, k)

0 commit comments

Comments
 (0)