@@ -6,11 +6,18 @@ package instana
6
6
import (
7
7
"errors"
8
8
"fmt"
9
+ "os"
10
+ "path/filepath"
9
11
"strconv"
10
12
"strings"
11
13
"time"
14
+
15
+ "github.com/stretchr/testify/assert/yaml"
12
16
)
13
17
18
+ // MaxEnvValueSize is the maximum size of the value of an environment variable.
19
+ const MaxEnvValueSize = 32 * 1024
20
+
14
21
// parseInstanaTags parses the tags string passed via INSTANA_TAGS.
15
22
// The tag string is a comma-separated list of keys optionally followed by an '=' character and a string value:
16
23
//
@@ -107,3 +114,148 @@ func parseInstanaTimeout(s string) (time.Duration, error) {
107
114
108
115
return time .Duration (ms ) * time .Millisecond , nil
109
116
}
117
+
118
+ // parseInstanaTracingDisable processes the INSTANA_TRACING_DISABLE environment variable value
119
+ // and updates the TracerOptions.Disable map accordingly.
120
+ //
121
+ // When the value is a boolean (true/false), the whole tracing feature is disabled/enabled.
122
+ // When a list of category or type names is specified, only those will be disabled.
123
+ //
124
+ // Examples:
125
+ // INSTANA_TRACING_DISABLE=true - disables all tracing
126
+ // INSTANA_TRACING_DISABLE="logging" - disables logging category
127
+ func parseInstanaTracingDisable (value string , opts * TracerOptions ) {
128
+ // Initialize the Disable map if it doesn't exist
129
+ if opts .DisableSpans == nil {
130
+ opts .DisableSpans = make (map [string ]bool )
131
+ }
132
+
133
+ // Trim spaces from the value
134
+ value = strings .TrimSpace (value )
135
+
136
+ // if it's a boolean value, disable all categories
137
+ if strings .EqualFold (value , "true" ) {
138
+ opts .DisableAllCategories ()
139
+ return
140
+ }
141
+
142
+ // if it's not a boolean value, process as a comma-separated list and disable each category.
143
+ items := strings .Split (value , "," )
144
+ for _ , item := range items {
145
+ item = strings .TrimSpace (item )
146
+ if item != "" {
147
+ opts .DisableSpans [item ] = true
148
+ }
149
+ }
150
+ }
151
+
152
+ // parseConfigFile reads and parses the YAML configuration file at the given path
153
+ // and updates the TracerOptions accordingly.
154
+ //
155
+ // The YAML file must follow this format:
156
+ // tracing:
157
+ // disable:
158
+ // - logging: true
159
+
160
+ func parseConfigFile (path string , opts * TracerOptions ) error {
161
+ // Validate the file path and security considerations
162
+ absPath , err := validateFile (path )
163
+ if err != nil {
164
+ return fmt .Errorf ("config file validation failed for %s: %w" , path , err )
165
+ }
166
+
167
+ // Read the file with proper error handling
168
+ data , err := os .ReadFile (absPath )
169
+ if err != nil {
170
+ return fmt .Errorf ("failed to read config file: %w" , err )
171
+ }
172
+
173
+ type Config struct {
174
+ Tracing struct {
175
+ Disable []map [string ]bool `yaml:"disable"`
176
+ } `yaml:"tracing"`
177
+ }
178
+
179
+ var config Config
180
+ if err := yaml .Unmarshal (data , & config ); err != nil {
181
+ return fmt .Errorf ("failed to parse YAML: %w" , err )
182
+ }
183
+
184
+ if opts .DisableSpans == nil {
185
+ opts .DisableSpans = make (map [string ]bool )
186
+ }
187
+
188
+ // Add the categories configured in the YAML file to the Disable map
189
+ for _ , disableMap := range config .Tracing .Disable {
190
+ for category , enabled := range disableMap {
191
+ if enabled {
192
+ opts .DisableSpans [category ] = true
193
+ }
194
+ }
195
+
196
+ }
197
+
198
+ return nil
199
+ }
200
+
201
+ // validateFile ensures the given config file path is safe and usable.
202
+ // Security considerations:
203
+ // - Resolves symlinks to prevent symlink attacks
204
+ // - Ensures the path exists and is a regular file
205
+ // - Enforces a reasonable file size limit to avoid DoS
206
+ // - Warns if file permissions are too permissive (world-readable)
207
+ func validateFile (path string ) (absPath string , err error ) {
208
+ // Resolve symlinks to avoid symlink attacks
209
+ realPath , err := filepath .EvalSymlinks (path )
210
+ if err != nil {
211
+ return absPath , fmt .Errorf ("failed to resolve config file path: %w" , err )
212
+ }
213
+
214
+ // Get absolute normalized path
215
+ absPath , err = filepath .Abs (realPath )
216
+ if err != nil {
217
+ return absPath , fmt .Errorf ("failed to get absolute path: %w" , err )
218
+ }
219
+
220
+ // Check if the path exists and is a regular file
221
+ fileInfo , err := os .Stat (absPath )
222
+ if err != nil {
223
+ return absPath , fmt .Errorf ("failed to access config file: %w" , err )
224
+ }
225
+
226
+ // Ensure it's a regular file, not a directory or special file
227
+ if ! fileInfo .Mode ().IsRegular () {
228
+ return absPath , fmt .Errorf ("config path is not a regular file: %s" , absPath )
229
+ }
230
+
231
+ // Enforce a maximum file size
232
+ const maxFileSize = 1 * 1024 * 1024 // 1MB
233
+ if fileInfo .Size () > maxFileSize {
234
+ return absPath , fmt .Errorf ("config file too large: %d bytes (max allowed: %d bytes)" ,
235
+ fileInfo .Size (), maxFileSize )
236
+ }
237
+
238
+ // Warn if the file is world-readable (optional hardening)
239
+ if fileInfo .Mode ().Perm ()& 0004 != 0 {
240
+ defaultLogger .Warn ("config file is world-readable, consider restricting permissions: " , absPath )
241
+ }
242
+
243
+ return absPath , nil
244
+ }
245
+
246
+ // LookupValidatedEnv retrieves the value of the environment variable named by key.
247
+ // It validates if env value exceeds the configured MaxEnvValueSize limit.
248
+ // On success, it returns the variable's value.
249
+ func lookupValidatedEnv (key string ) (string , bool ) {
250
+ envVal , ok := os .LookupEnv (key )
251
+ if ! ok {
252
+ return "" , false
253
+ }
254
+
255
+ if len (envVal ) > MaxEnvValueSize {
256
+ defaultLogger .Error (fmt .Errorf ("value of %q exceeds safe limit (%d bytes)" , key , MaxEnvValueSize ))
257
+ return "" , false
258
+ }
259
+
260
+ return envVal , true
261
+ }
0 commit comments