Skip to content

Commit

Permalink
Include information about sensitive keys in Program Resolver (#531)
Browse files Browse the repository at this point in the history
Connect the dots between Owl Store and Program Resolver with a low
amount of coupling. Likely needs a refactor but works for now 👌.
  • Loading branch information
sourishkrout committed Mar 14, 2024
1 parent 9d19f68 commit 22daf6e
Show file tree
Hide file tree
Showing 15 changed files with 581 additions and 159 deletions.
3 changes: 3 additions & 0 deletions internal/api/runme/runner/v1/runner.proto
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ message ResolveProgramResponse {
STATUS_UNRESOLVED_WITH_PLACEHOLDER = 2;
// resolved means that the variable is resolved.
STATUS_RESOLVED = 3;
// unresolved with secret means that the variable is unresolved
// and it requires treatment as a secret.
STATUS_UNRESOLVED_WITH_SECRET = 4;
}
}

Expand Down
31 changes: 26 additions & 5 deletions internal/command/program_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,18 @@ type ProgramResolver struct {
mode ProgramResolverMode
sources []ProgramResolverSource

envCache *sync.Map
sensitiveEnvNames []string
envCache *sync.Map

astPrinter *syntax.Printer
}

func NewProgramResolver(mode ProgramResolverMode, sources ...ProgramResolverSource) *ProgramResolver {
func NewProgramResolver(mode ProgramResolverMode, sensitiveEnvNames []string, sources ...ProgramResolverSource) *ProgramResolver {
return &ProgramResolver{
mode: mode,
sources: sources,
astPrinter: syntax.NewPrinter(),
mode: mode,
sensitiveEnvNames: sensitiveEnvNames,
sources: sources,
astPrinter: syntax.NewPrinter(),
}
}

Expand All @@ -62,6 +64,9 @@ const (
// ProgramResolverStatusUnresolvedWithPlaceholder indicates a variable is unresolved but it has a placeholder.
// It typically means that the variable is of form `export FOO="this is a placeholder"`.
ProgramResolverStatusUnresolvedWithPlaceholder
// ProgramResolverStatusUnresolvedWithSecret indicates a variable is unresolved and needs to be treated with sensitivity.
// It typically means that the variable is a password, certificate, or access key.
ProgramResolverStatusUnresolvedWithSecret
// ProgramResolverStatusResolved indicates a variable is resolved.
ProgramResolverStatusResolved
)
Expand Down Expand Up @@ -114,6 +119,7 @@ func (r *ProgramResolver) Resolve(reader io.Reader, writer io.Writer) (*ProgramR
name := decl.Args[0].Name.Value
originalValue, isPlaceholder := r.findOriginalValue(decl)
resolvedValue, hasResolvedValue := r.findEnvValue(name)
isSensitive := r.IsEnvSensitive(name)

varResult := ProgramResolverVarResult{
Status: ProgramResolverStatusUnresolved,
Expand All @@ -124,6 +130,10 @@ func (r *ProgramResolver) Resolve(reader io.Reader, writer io.Writer) (*ProgramR

switch r.mode {
case ProgramResolverModePromptAll:
if isSensitive {
varResult.Status = ProgramResolverStatusUnresolvedWithSecret
break
}
if hasResolvedValue {
varResult.Status = ProgramResolverStatusUnresolvedWithPlaceholder
break
Expand All @@ -142,6 +152,8 @@ func (r *ProgramResolver) Resolve(reader io.Reader, writer io.Writer) (*ProgramR
default:
if hasResolvedValue {
varResult.Status = ProgramResolverStatusResolved
} else if isSensitive {
varResult.Status = ProgramResolverStatusUnresolvedWithSecret
} else if isPlaceholder {
varResult.Status = ProgramResolverStatusUnresolvedWithPlaceholder
} else if originalValue != "" {
Expand Down Expand Up @@ -254,6 +266,15 @@ func (r *ProgramResolver) findEnvValue(name string) (string, bool) {
return "", ok
}

func (r *ProgramResolver) IsEnvSensitive(name string) bool {
for _, key := range r.sensitiveEnvNames {
if key == name {
return true
}
}
return false
}

func (r *ProgramResolver) collectEnvFromSources() {
if r.envCache != nil {
return
Expand Down
62 changes: 57 additions & 5 deletions internal/command/program_resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func TestProgramResolverResolve(t *testing.T) {

for _, tc := range testCases {
t.Run("ProgramResolverModeAuto_"+tc.name, func(t *testing.T) {
r := NewProgramResolver(ProgramResolverModeAuto)
r := NewProgramResolver(ProgramResolverModeAuto, []string{})
buf := bytes.NewBuffer(nil)
got, err := r.Resolve(strings.NewReader(tc.program), buf)
require.NoError(t, err)
Expand All @@ -175,7 +175,7 @@ func TestProgramResolverResolve(t *testing.T) {
})

t.Run("ProgramResolverModeSkip_"+tc.name, func(t *testing.T) {
r := NewProgramResolver(ProgramResolverModeSkipAll)
r := NewProgramResolver(ProgramResolverModeSkipAll, []string{})
buf := bytes.NewBuffer(nil)
got, err := r.Resolve(strings.NewReader(tc.program), buf)
require.NoError(t, err)
Expand All @@ -194,7 +194,11 @@ func TestProgramResolverResolve(t *testing.T) {
}

func TestProgramResolverResolve_ProgramResolverModeAuto(t *testing.T) {
r := NewProgramResolver(ProgramResolverModeAuto, ProgramResolverSourceFunc([]string{"MY_ENV=resolved"}))
r := NewProgramResolver(
ProgramResolverModeAuto,
[]string{},
ProgramResolverSourceFunc([]string{"MY_ENV=resolved"}),
)
buf := bytes.NewBuffer(nil)
result, err := r.Resolve(strings.NewReader(`export MY_ENV=default`), buf)
require.NoError(t, err)
Expand All @@ -218,7 +222,11 @@ func TestProgramResolverResolve_ProgramResolverModeAuto(t *testing.T) {

func TestProgramResolverResolve_ProgramResolverModePrompt(t *testing.T) {
t.Run("Prompt with message", func(t *testing.T) {
r := NewProgramResolver(ProgramResolverModePromptAll, ProgramResolverSourceFunc([]string{"MY_ENV=resolved"}))
r := NewProgramResolver(
ProgramResolverModePromptAll,
[]string{},
ProgramResolverSourceFunc([]string{"MY_ENV=resolved"}),
)
buf := bytes.NewBuffer(nil)
result, err := r.Resolve(strings.NewReader(`export MY_ENV=message value`), buf)
require.NoError(t, err)
Expand All @@ -241,7 +249,11 @@ func TestProgramResolverResolve_ProgramResolverModePrompt(t *testing.T) {
})

t.Run("Prompt with placeholder", func(t *testing.T) {
r := NewProgramResolver(ProgramResolverModePromptAll, ProgramResolverSourceFunc([]string{"MY_ENV=resolved"}))
r := NewProgramResolver(
ProgramResolverModePromptAll,
[]string{},
ProgramResolverSourceFunc([]string{"MY_ENV=resolved"}),
)
buf := bytes.NewBuffer(nil)
result, err := r.Resolve(strings.NewReader(`export MY_ENV="placeholder value"`), buf)
require.NoError(t, err)
Expand All @@ -263,3 +275,43 @@ func TestProgramResolverResolve_ProgramResolverModePrompt(t *testing.T) {
require.EqualValues(t, "#\n# MY_ENV set in smart env store\n# \"export MY_ENV=\\\"placeholder value\\\"\"\n\n", buf.String())
})
}

func TestProgramResolverResolve_SensitiveEnvKeys(t *testing.T) {
t.Run("Prompt with message", func(t *testing.T) {
r := NewProgramResolver(
ProgramResolverModePromptAll,
[]string{"MY_PASSWORD", "MY_SECRET"},
)
buf := bytes.NewBuffer(nil)
result, err := r.Resolve(strings.NewReader("export MY_PASSWORD=super-secret\nexport MY_SECRET=also-secret\nexport MY_PLAIN=text\n"), buf)
require.NoError(t, err)
require.EqualValues(
t,
&ProgramResolverResult{
ModifiedProgram: true,
Variables: []ProgramResolverVarResult{
{
Status: ProgramResolverStatusUnresolvedWithSecret,
Name: "MY_PASSWORD",
OriginalValue: "super-secret",
Value: "",
},
{
Status: ProgramResolverStatusUnresolvedWithSecret,
Name: "MY_SECRET",
OriginalValue: "also-secret",
Value: "",
},
{
Status: ProgramResolverStatusUnresolvedWithMessage,
Name: "MY_PLAIN",
OriginalValue: "text",
Value: "",
},
},
},
result,
)
require.EqualValues(t, "#\n# MY_PASSWORD set in smart env store\n# \"export MY_PASSWORD=super-secret\"\n#\n# MY_SECRET set in smart env store\n# \"export MY_SECRET=also-secret\"\n#\n# MY_PLAIN set in smart env store\n# \"export MY_PLAIN=text\"\n\n", buf.String())
})
}
Loading

0 comments on commit 22daf6e

Please sign in to comment.