forked from gravitational/teleport
-
Notifications
You must be signed in to change notification settings - Fork 0
/
environment.go
188 lines (157 loc) · 5.67 KB
/
environment.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
/*
* Teleport
* Copyright (C) 2023 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package envutils
import (
"bufio"
"fmt"
"io"
"os"
"strings"
log "github.com/sirupsen/logrus"
"github.com/gravitational/teleport"
"github.com/gravitational/teleport/lib/utils"
)
// ReadEnvironmentFile will read environment variables from a passed in location.
// Lines that start with "#" or empty lines are ignored. Assignments are in the
// form name=value and no variable expansion occurs.
func ReadEnvironmentFile(filename string) ([]string, error) {
// open the users environment file. if we don't find a file, move on as
// having this file for the user is optional.
file, err := utils.OpenFileNoUnsafeLinks(filename)
if err != nil {
log.Warnf("Unable to open environment file %v: %v, skipping", filename, err)
return []string{}, nil
}
defer file.Close()
return readEnvironment(file)
}
func readEnvironment(r io.Reader) ([]string, error) {
var lineno int
env := &SafeEnv{}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
// follow the lead of OpenSSH and don't allow more than 1,000 environment variables
// https://github.com/openssh/openssh-portable/blob/master/session.c#L873-L874
lineno = lineno + 1
if lineno > teleport.MaxEnvironmentFileLines {
log.Warnf("Too many lines in environment file, returning first %v lines", teleport.MaxEnvironmentFileLines)
return *env, nil
}
// empty lines or lines that start with # are ignored
if line == "" || line[0] == '#' {
continue
}
// split on first =, if not found, log it and continue
idx := strings.Index(line, "=")
if idx == -1 {
log.Debugf("Bad line %v while reading environment file: no = separator found", lineno)
continue
}
// split key and value and make sure that key has a name
key := line[:idx]
value := line[idx+1:]
if strings.TrimSpace(key) == "" {
log.Debugf("Bad line %v while reading environment file: key without name", lineno)
continue
}
// key is added trusted within this context, but should be "AddFullUnique" when combined with any other values
env.AddTrusted(key, value)
}
if err := scanner.Err(); err != nil {
log.Warnf("Unable to read environment file: %v", err)
return []string{}, nil
}
return *env, nil
}
var unsafeEnvironmentPrefixes = []string{
// Linux
// Covering cases from LD (man ld.so) to prevent injection like LD_PRELOAD
"LD_",
// macOS
// Covering cases from DYLD (man dyld) to prevent injection like DYLD_LIBRARY_PATH
"DYLD_",
}
// SafeEnv allows you to build a system environment while avoiding potentially dangerous environment conditions. In
// addition, SafeEnv will ignore any values added if the key already exists. This allows earlier inserts to take
// priority and ensure there is no conflicting values.
type SafeEnv []string
// AddTrusted will add the key and value to the environment if it's a safe value to forward on for fork / exec. This
// will not check for duplicates.
func (e *SafeEnv) AddTrusted(k, v string) {
e.add(false, k, v)
}
// AddUnique will add the key and value to the environment if it's a safe value to forward on for fork / exec. If the
// key already exists (case-insensitive) it will be ignored.
func (e *SafeEnv) AddUnique(k, v string) {
e.add(true, k, v)
}
func (e *SafeEnv) add(preventDuplicates bool, k, v string) {
k = strings.TrimSpace(k)
v = strings.TrimSpace(v)
if e.isUnsafeKey(preventDuplicates, k) {
return
}
*e = append(*e, fmt.Sprintf("%s=%s", k, v))
}
// AddFullTrusted adds an exact value, in the KEY=VALUE format. This should only be used if they values are already
// combined. When the values are separate the [Add] function is generally preferred. This will not check for
// duplicates.
func (e *SafeEnv) AddFullTrusted(fullValues ...string) {
e.addFull(false, fullValues)
}
// AddFullUnique adds an exact value, in the KEY=VALUE format. This should only be used if they values are already
// combined. When the values are separate the [Add] function is generally preferred. If any keys already exists
// (case-insensitive) they will be ignored.
func (e *SafeEnv) AddFullUnique(fullValues ...string) {
e.addFull(true, fullValues)
}
func (e *SafeEnv) addFull(preventDuplicates bool, fullValues []string) {
for _, kv := range fullValues {
kv = strings.TrimSpace(kv)
key := strings.SplitN(kv, "=", 2)[0]
if e.isUnsafeKey(preventDuplicates, key) {
continue
}
*e = append(*e, kv)
}
}
func (e *SafeEnv) isUnsafeKey(preventDuplicates bool, key string) bool {
if key == "" || key == "=" {
return false
}
upperKey := strings.ToUpper(key)
for _, prefix := range unsafeEnvironmentPrefixes {
if strings.HasPrefix(upperKey, prefix) {
return true
}
}
if preventDuplicates {
prefix := upperKey + "="
for _, kv := range *e {
if strings.HasPrefix(strings.ToUpper(kv), prefix) {
return true
}
}
}
return false
}
// AddExecEnvironment will add safe values from [os.Environ], ignoring any duplicates that may have already been added.
func (e *SafeEnv) AddExecEnvironment() {
e.addFull(true, os.Environ())
}