Skip to content

Commit 523b2bf

Browse files
committed
implement preserving env from host into vm in shell command
Signed-off-by: olalekan odukoya <odukoyaonline@gmail.com>
1 parent 96c7179 commit 523b2bf

File tree

5 files changed

+428
-2
lines changed

5 files changed

+428
-2
lines changed

cmd/limactl/shell.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"github.com/sirupsen/logrus"
2020
"github.com/spf13/cobra"
2121

22+
"github.com/lima-vm/lima/v2/pkg/envutil"
2223
"github.com/lima-vm/lima/v2/pkg/instance"
2324
"github.com/lima-vm/lima/v2/pkg/ioutilx"
2425
"github.com/lima-vm/lima/v2/pkg/limayaml"
@@ -54,6 +55,7 @@ func newShellCommand() *cobra.Command {
5455
shellCmd.Flags().String("shell", "", "Shell interpreter, e.g. /bin/bash")
5556
shellCmd.Flags().String("workdir", "", "Working directory")
5657
shellCmd.Flags().Bool("reconnect", false, "Reconnect to the SSH session")
58+
shellCmd.Flags().Bool("preserve-env", false, "Propagate environment variables to the shell")
5759
return shellCmd
5860
}
5961

@@ -178,7 +180,24 @@ func shellAction(cmd *cobra.Command, args []string) error {
178180
} else {
179181
shell = shellescape.Quote(shell)
180182
}
181-
script := fmt.Sprintf("%s ; exec %s --login", changeDirCmd, shell)
183+
// Handle environment variable propagation
184+
var envPrefix string
185+
preserveEnv, err := cmd.Flags().GetBool("preserve-env")
186+
if err != nil {
187+
return err
188+
}
189+
if preserveEnv {
190+
filteredEnv := envutil.FilterEnvironment()
191+
if len(filteredEnv) > 0 {
192+
envVars := make([]string, len(filteredEnv))
193+
for i, envVar := range filteredEnv {
194+
envVars[i] = shellescape.Quote(envVar)
195+
}
196+
envPrefix = "env " + strings.Join(envVars, " ") + " "
197+
}
198+
}
199+
200+
script := fmt.Sprintf("%s ; exec %s%s --login", changeDirCmd, envPrefix, shell)
182201
if len(args) > 1 {
183202
quotedArgs := make([]string, len(args[1:]))
184203
parsingEnv := true

cmd/nerdctl.lima

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
#!/bin/sh
22
set -eu
3-
exec lima nerdctl "$@"
3+
exec limactl shell --preserve-env default nerdctl "$@"

pkg/envutil/envutil.go

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// SPDX-FileCopyrightText: Copyright The Lima Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package envutil
5+
6+
import (
7+
"os"
8+
"strings"
9+
10+
"github.com/sirupsen/logrus"
11+
)
12+
13+
// BuiltinBlocklist contains environment variables that should not be propagated by default.
14+
var BuiltinBlocklist = []string{
15+
"BASH*",
16+
"DISPLAY",
17+
"DYLD_*",
18+
"EUID",
19+
"FPATH",
20+
"GID",
21+
"GROUP",
22+
"HOME",
23+
"HOSTNAME",
24+
"LD_*",
25+
"LOGNAME",
26+
"OLDPWD",
27+
"PATH",
28+
"PWD",
29+
"SHELL",
30+
"SHLVL",
31+
"SSH_*",
32+
"TERM",
33+
"TERMINFO",
34+
"TMPDIR",
35+
"UID",
36+
"USER",
37+
"XAUTHORITY",
38+
"XDG_*",
39+
"ZDOTDIR",
40+
"ZSH*",
41+
"_*", // Variables starting with underscore are typically internal
42+
}
43+
44+
func getBlockList() []string {
45+
if blockEnv := os.Getenv("LIMA_SHELLENV_BLOCK"); blockEnv != "" {
46+
if strings.HasPrefix(blockEnv, "+") {
47+
additionalBlocks := parseEnvList(blockEnv[1:])
48+
blockList := make([]string, len(BuiltinBlocklist)+len(additionalBlocks))
49+
copy(blockList, BuiltinBlocklist)
50+
copy(blockList[len(BuiltinBlocklist):], additionalBlocks)
51+
return blockList
52+
}
53+
return parseEnvList(blockEnv)
54+
}
55+
return BuiltinBlocklist
56+
}
57+
58+
func getAllowList() []string {
59+
if allowEnv := os.Getenv("LIMA_SHELLENV_ALLOW"); allowEnv != "" {
60+
return parseEnvList(allowEnv)
61+
}
62+
return nil
63+
}
64+
65+
func IsUsingBuiltinBlocklist() bool {
66+
blockEnv := os.Getenv("LIMA_SHELLENV_BLOCK")
67+
return blockEnv == "" || strings.HasPrefix(blockEnv, "+")
68+
}
69+
70+
func parseEnvList(envList string) []string {
71+
if envList == "" {
72+
return nil
73+
}
74+
75+
parts := strings.Split(envList, ",")
76+
result := make([]string, 0, len(parts))
77+
for _, part := range parts {
78+
if trimmed := strings.TrimSpace(part); trimmed != "" {
79+
result = append(result, trimmed)
80+
}
81+
}
82+
83+
return result
84+
}
85+
86+
func matchesPattern(name, pattern string) bool {
87+
if pattern == name {
88+
return true
89+
}
90+
91+
if strings.HasSuffix(pattern, "*") {
92+
prefix := pattern[:len(pattern)-1]
93+
return strings.HasPrefix(name, prefix)
94+
}
95+
96+
return false
97+
}
98+
99+
func matchesAnyPattern(name string, patterns []string) bool {
100+
for _, pattern := range patterns {
101+
if matchesPattern(name, pattern) {
102+
return true
103+
}
104+
}
105+
return false
106+
}
107+
108+
func FilterEnvironment() []string {
109+
return FilterEnvironmentWithLists(os.Environ(), getBlockList(), getAllowList())
110+
}
111+
112+
func FilterEnvironmentWithLists(env, blockList, allowList []string) []string {
113+
var filtered []string
114+
115+
for _, envVar := range env {
116+
parts := strings.SplitN(envVar, "=", 2)
117+
if len(parts) != 2 {
118+
continue
119+
}
120+
121+
name := parts[0]
122+
123+
if len(allowList) > 0 {
124+
if !matchesAnyPattern(name, allowList) {
125+
continue
126+
}
127+
filtered = append(filtered, envVar)
128+
continue
129+
}
130+
131+
if matchesAnyPattern(name, blockList) {
132+
logrus.Debugf("Blocked env variable %q", name)
133+
continue
134+
}
135+
136+
filtered = append(filtered, envVar)
137+
}
138+
139+
return filtered
140+
}
141+
142+
func GetBuiltinBlocklist() []string {
143+
result := make([]string, len(BuiltinBlocklist))
144+
copy(result, BuiltinBlocklist)
145+
return result
146+
}

0 commit comments

Comments
 (0)