diff --git a/internal/dynamo-browse/controllers/scripts.go b/internal/dynamo-browse/controllers/scripts.go index 09eca28..f279cb8 100644 --- a/internal/dynamo-browse/controllers/scripts.go +++ b/internal/dynamo-browse/controllers/scripts.go @@ -65,6 +65,7 @@ func (sc *ScriptController) Init() { OSExecShell: "/bin/bash", Permissions: scriptmanager.Permissions{ AllowShellCommands: true, + AllowEnv: true, }, }) } diff --git a/internal/dynamo-browse/services/scriptmanager/modos.go b/internal/dynamo-browse/services/scriptmanager/modos.go index 4a1d1c5..7308467 100644 --- a/internal/dynamo-browse/services/scriptmanager/modos.go +++ b/internal/dynamo-browse/services/scriptmanager/modos.go @@ -5,6 +5,7 @@ import ( "github.com/cloudcmds/tamarin/arg" "github.com/cloudcmds/tamarin/object" "github.com/cloudcmds/tamarin/scope" + "os" "os/exec" ) @@ -35,12 +36,35 @@ func (om *osModule) exec(ctx context.Context, args ...object.Object) object.Obje return object.NewOkResult(object.NewString(string(out))) } +func (om *osModule) env(ctx context.Context, args ...object.Object) object.Object { + if err := arg.Require("os.env", 1, args); err != nil { + return err + } + + cmdEnvName, objErr := object.AsString(args[0]) + if objErr != nil { + return objErr + } + + opts := optionFromCtx(ctx) + if !opts.Permissions.AllowEnv { + return object.Nil + } + + envVal, hasVal := os.LookupEnv(cmdEnvName) + if !hasVal { + return object.Nil + } + return object.NewString(envVal) +} + func (om *osModule) register(scp *scope.Scope) { modScope := scope.New(scope.Opts{}) mod := object.NewModule("os", modScope) modScope.AddBuiltins([]*object.Builtin{ object.NewBuiltin("exec", om.exec, mod), + object.NewBuiltin("env", om.env, mod), }) scp.Declare("os", mod, true) diff --git a/internal/dynamo-browse/services/scriptmanager/modos_test.go b/internal/dynamo-browse/services/scriptmanager/modos_test.go index 03f36ed..d9823e9 100644 --- a/internal/dynamo-browse/services/scriptmanager/modos_test.go +++ b/internal/dynamo-browse/services/scriptmanager/modos_test.go @@ -9,6 +9,62 @@ import ( "testing" ) +func TestOSModule_Env(t *testing.T) { + t.Run("should return value of environment variables", func(t *testing.T) { + t.Setenv("FULL_VALUE", "this is a value") + t.Setenv("EMPTY_VALUE", "") + + testFS := testScriptFile(t, "test.tm", ` + assert(os.env("FULL_VALUE") == "this is a value") + assert(os.env("EMPTY_VALUE") == "") + assert(os.env("MISSING_VALUE") == nil) + + assert(bool(os.env("FULL_VALUE")) == true) + assert(bool(os.env("EMPTY_VALUE")) == false) + assert(bool(os.env("MISSING_VALUE")) == false) + `) + + srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowEnv: true, + }, + }) + + ctx := context.Background() + err := <-srv.RunAdHocScript(ctx, "test.tm") + assert.NoError(t, err) + }) + + t.Run("should return nil when no access to environment variables", func(t *testing.T) { + t.Setenv("FULL_VALUE", "this is a value") + t.Setenv("EMPTY_VALUE", "") + + testFS := testScriptFile(t, "test.tm", ` + assert(os.env("FULL_VALUE") == nil) + assert(os.env("EMPTY_VALUE") == nil) + assert(os.env("MISSING_VALUE") == nil) + + assert(bool(os.env("FULL_VALUE")) == false) + assert(bool(os.env("EMPTY_VALUE")) == false) + assert(bool(os.env("MISSING_VALUE")) == false) + `) + + srv := scriptmanager.New(scriptmanager.WithFS(testFS)) + srv.SetDefaultOptions(scriptmanager.Options{ + OSExecShell: "/bin/bash", + Permissions: scriptmanager.Permissions{ + AllowEnv: false, + }, + }) + + ctx := context.Background() + err := <-srv.RunAdHocScript(ctx, "test.tm") + assert.NoError(t, err) + }) +} + func TestOSModule_Exec(t *testing.T) { t.Run("should run command and return stdout", func(t *testing.T) { mockedUIService := mocks.NewUIService(t) diff --git a/internal/dynamo-browse/services/scriptmanager/opts.go b/internal/dynamo-browse/services/scriptmanager/opts.go index d40600c..06f4330 100644 --- a/internal/dynamo-browse/services/scriptmanager/opts.go +++ b/internal/dynamo-browse/services/scriptmanager/opts.go @@ -29,6 +29,9 @@ func (opts Options) configuredShell() string { type Permissions struct { // AllowShellCommands determines whether or not a script can execute shell commands. AllowShellCommands bool + + // AllowEnv determines whether or not a script can access environment variables + AllowEnv bool } type optionCtxKeyType struct{}