• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-connect / 1918425956

10 Jul 2025 09:22AM UTC coverage: 69.792% (+0.02%) from 69.769%
1918425956

Pull #158

gitlab-ci

danielskinstad
feat: set default ExpireAfterIdle to 10 minutes

Introduce a default value of 10 minutes for `ExpireAfterIdle`. This default applies
when `StopExpired` is true and both `ExpireAfter` and `ExpireAfterIdle` are unset, since
these two options are mutually exclusive.

Note that this is a behavioral change; no default value was previously
assigned to `ExpireAfterIdle`.

Changelog: Commit
Ticket: MEN-8260

Signed-off-by: Daniel Skinstad Drabitzius <daniel.drabitzius@northern.tech>
Pull Request #158: feat: set default ExpireAfterIdle to 10 minutes

4 of 7 new or added lines in 1 file covered. (57.14%)

1 existing line in 1 file now uncovered.

2479 of 3552 relevant lines covered (69.79%)

6.11 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

89.27
/config/config.go
1
// Copyright 2022 Northern.tech AS
2
//
3
//    Licensed under the Apache License, Version 2.0 (the "License");
4
//    you may not use this file except in compliance with the License.
5
//    You may obtain a copy of the License at
6
//
7
//        http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//    Unless required by applicable law or agreed to in writing, software
10
//    distributed under the License is distributed on an "AS IS" BASIS,
11
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//    See the License for the specific language governing permissions and
13
//    limitations under the License.
14

15
package config
16

17
import (
18
        "bufio"
19
        "encoding/json"
20
        "io/ioutil"
21
        "os"
22
        "os/user"
23
        "path/filepath"
24

25
        "time"
26

27
        "github.com/pkg/errors"
28
        log "github.com/sirupsen/logrus"
29
)
30

31
type TerminalConfig struct {
32
        Width  uint16
33
        Height uint16
34
        // Disable remote terminal
35
        Disable bool
36
}
37

38
type MenderClientConfig struct {
39
        // Disable mender-client websocket bindings.
40
        Disable bool
41
}
42

43
type FileTransferConfig struct {
44
        // Disable file transfer features
45
        Disable bool
46
}
47

48
type PortForwardConfig struct {
49
        // Disable port forwarding feature
50
        Disable bool
51
}
52

53
type SessionsConfig struct {
54
        // Whether to stop expired sessions
55
        StopExpired bool
56
        // Seconds after startup of a sessions that will make it expire
57
        ExpireAfter uint32
58
        // Seconds after last activity of a sessions that will make it expire
59
        ExpireAfterIdle uint32
60
        // Max sessions per user
61
        MaxPerUser uint32
62
}
63

64
// Counter for the limits  and restrictions for the File Transfer
65
// on and off the device(MEN-4325)
66
type RateLimits struct {
67
        // Maximum bytes count allowed to transfer per minute
68
        // this is per device global limit, which is consulted
69
        // every time there is a transfer starting. if above
70
        // the limit, we answer with error message indicating
71
        // limit reached.
72
        MaxBytesTxPerMinute uint64
73
        MaxBytesRxPerMinute uint64
74
}
75

76
// Limits and restrictions for the File Transfer on and off the device(MEN-4325)
77
type FileTransferLimits struct {
78
        // the global parent directory that File Transfer will never escape
79
        Chroot string
80
        // No way to escape Chroot, even if this one is set the Chroot setting will
81
        // be checked for the target of any link and restricted accordingly
82
        FollowSymLinks bool
83
        // Allow overwrite files
84
        AllowOverwrite bool
85
        // set the owner of new files to OwnerPut
86
        OwnerPut string
87
        // set the owner of new files to OwnerPut
88
        GroupPut string
89
        // allow to get only files owned by OwnerGet
90
        OwnerGet []string
91
        // allow to get only files owned by OwnerGet
92
        GroupGet []string
93
        // umask for new files
94
        Umask string
95
        // Maximum allowed file size
96
        MaxFileSize uint64
97
        // File transfer rate limits
98
        Counters RateLimits
99
        // If true it is allowed to upload files with set user id on execute bit set
100
        AllowSuid bool
101
        // By default we only allow to send/put regular files
102
        RegularFilesOnly bool
103
        // By default we preserve the file modes but set one according to
104
        //the current umask or configured Umask above
105
        PreserveMode bool
106
        // By default we preserve the owner of the file uploaded
107
        PreserveOwner bool
108
}
109

110
type Limits struct {
111
        Enabled      bool               `json:"Enabled"`
112
        FileTransfer FileTransferLimits `json:"FileTransfer"`
113
}
114

115
// MenderShellConfigFromFile holds the configuration settings read from the config file
116
type MenderShellConfigFromFile struct {
117
        // The command to run as shell
118
        ShellCommand string
119
        // ShellArguments is the arguments the shell is launched with. Defaults
120
        // to '--login'.
121
        ShellArguments []string
122
        // Name of the user who owns the shell process
123
        User string
124
        // Terminal settings
125
        Terminal TerminalConfig `json:"Terminal"`
126
        // User sessions settings
127
        Sessions SessionsConfig `json:"Sessions"`
128
        // Limits and restrictions
129
        Limits Limits `json:"Limits"`
130
        // Reconnect interval
131
        ReconnectIntervalSeconds int
132
        // FileTransfer config
133
        FileTransfer FileTransferConfig
134
        // PortForward config
135
        PortForward PortForwardConfig
136
        // MenderClient config
137
        MenderClient MenderClientConfig
138
}
139

140
// MenderShellConfigDeprecated holds the deprecated configuration settings
141
type MenderShellConfigDeprecated struct {
142
        // Server URL (For single server conf)
143
        ServerURL string
144
        // List of available servers, to which client can fall over
145
        Servers []struct {
146
                ServerURL string
147
        }
148
        // ClientProtocol "https"
149
        ClientProtocol string
150
        // HTTPS client parameters
151
        HTTPSClient struct {
152
                Certificate string
153
                Key         string
154
                SSLEngine   string
155
        } `json:"HttpsClient"`
156
        // Skip CA certificate validation
157
        SkipVerify bool
158
        // Path to server SSL certificate
159
        ServerCertificate string
160
}
161

162
// MenderShellConfig holds the configuration settings for the Mender shell client
163
type MenderShellConfig struct {
164
        MenderShellConfigFromFile
165
        Debug bool
166
        Trace bool
167
}
168

169
// NewMenderShellConfig initializes a new MenderShellConfig struct
170
func NewMenderShellConfig() *MenderShellConfig {
27✔
171
        return &MenderShellConfig{
27✔
172
                MenderShellConfigFromFile: MenderShellConfigFromFile{},
27✔
173
        }
27✔
174
}
27✔
175

176
// LoadConfig parses the mender configuration json-files
177
// (/etc/mender/mender-connect.conf and /var/lib/mender/mender-connect.conf)
178
// and loads the values into the MenderShellConfig structure defining high level
179
// client configurations.
180
func LoadConfig(mainConfigFile string, fallbackConfigFile string) (*MenderShellConfig, error) {
25✔
181
        // Load fallback configuration first, then main configuration.
25✔
182
        // It is OK if either file does not exist, so long as the other one does exist.
25✔
183
        // It is also OK if both files exist.
25✔
184
        // Because the main configuration is loaded last, its option values
25✔
185
        // override those from the fallback file, for options present in both files.
25✔
186
        var filesLoadedCount int
25✔
187
        config := NewMenderShellConfig()
25✔
188

25✔
189
        if loadErr := loadConfigFile(fallbackConfigFile, config, &filesLoadedCount); loadErr != nil {
26✔
190
                return nil, loadErr
1✔
191
        }
1✔
192

193
        if loadErr := loadConfigFile(mainConfigFile, config, &filesLoadedCount); loadErr != nil {
27✔
194
                return nil, loadErr
3✔
195
        }
3✔
196

197
        log.Debugf("Loaded %d configuration file(s)", filesLoadedCount)
21✔
198
        if filesLoadedCount == 0 {
22✔
199
                log.Info("No configuration files present. Using defaults")
1✔
200
                return config, nil
1✔
201
        }
1✔
202

203
        log.Debugf("Loaded configuration = %#v", config)
20✔
204
        return config, nil
20✔
205
}
206

207
func isExecutable(path string) bool {
9✔
208
        info, _ := os.Stat(path)
9✔
209
        if info == nil {
10✔
210
                return false
1✔
211
        }
1✔
212
        mode := info.Mode()
8✔
213
        return (mode & 0111) != 0
8✔
214
}
215

216
func isInShells(path string) bool {
3✔
217
        file, err := os.Open("/etc/shells")
3✔
218
        if err != nil {
3✔
219
                // if no /etc/shell is found, DefaultShellCommand is accepted
×
220
                if path == DefaultShellCommand {
×
221
                        return true
×
222
                }
×
223
                log.Fatal(err)
×
224
        }
225
        defer file.Close()
3✔
226
        scanner := bufio.NewScanner(file)
3✔
227
        found := false
3✔
228
        for scanner.Scan() {
16✔
229
                if scanner.Text() == path {
15✔
230
                        found = true
2✔
231
                        break
2✔
232
                }
233
        }
234
        return found
3✔
235
}
236

237
func validateUser(c *MenderShellConfig) (err error) {
7✔
238
        if c.User == "" {
10✔
239
                return errors.New("please provide a user to run the shell as")
3✔
240
        }
3✔
241
        u, err := user.Lookup(c.User)
4✔
242
        if err == nil && u == nil {
4✔
243
                return errors.New("unknown error while getting a user id")
×
244
        }
×
245
        if err != nil {
5✔
246
                return err
1✔
247
        }
1✔
248
        return nil
3✔
249
}
250

251
func (c *MenderShellConfig) applyDefaults() error {
10✔
252
        //check if shell is given, if not, defaulting to /bin/sh
10✔
253
        if c.ShellCommand == "" {
12✔
254
                log.Warnf("ShellCommand is empty, defaulting to %s", DefaultShellCommand)
2✔
255
                c.ShellCommand = DefaultShellCommand
2✔
256
        }
2✔
257

258
        if c.ShellArguments == nil {
18✔
259
                log.Warnf("ShellArguments is empty, defaulting to %s", DefaultShellArguments)
8✔
260
                c.ShellArguments = DefaultShellArguments
8✔
261
        }
8✔
262

263
        if c.Terminal.Width == 0 {
18✔
264
                c.Terminal.Width = DefaultTerminalWidth
8✔
265
        }
8✔
266

267
        if c.Terminal.Height == 0 {
18✔
268
                c.Terminal.Height = DefaultTerminalHeight
8✔
269
        }
8✔
270

271
        if !c.Sessions.StopExpired {
18✔
272
                c.Sessions.ExpireAfter = 0
8✔
273
                c.Sessions.ExpireAfterIdle = 0
8✔
274
        } else {
10✔
275
                if c.Sessions.ExpireAfter > 0 && c.Sessions.ExpireAfterIdle > 0 {
4✔
276
                        log.Warnf("Both ExpireAfter and ExpireAfterIdle specified; " +
2✔
277
                                "they are mutually exclusive")
2✔
278
                }
2✔
279
                if c.Sessions.ExpireAfter == 0 && c.Sessions.ExpireAfterIdle == 0 {
2✔
NEW
280
                        log.Infof("Defaulting ExpireAfterIdle to %s",
×
NEW
281
                                time.Second*time.Duration(c.Sessions.ExpireAfterIdle))
×
NEW
282
                        c.Sessions.ExpireAfterIdle = DefaultExpireAfterIdle
×
UNCOV
283
                }
×
284
        }
285

286
        if c.ReconnectIntervalSeconds == 0 {
20✔
287
                c.ReconnectIntervalSeconds = DefaultReconnectIntervalsSeconds
10✔
288
        }
10✔
289

290
        // permit by default, probably will be changed after integration test is modified
291
        c.Limits.FileTransfer.PreserveMode = true
10✔
292
        c.Limits.FileTransfer.PreserveOwner = true
10✔
293

10✔
294
        return nil
10✔
295
}
296

297
// Validate verifies the Servers fields in the configuration
298
func (c *MenderShellConfig) Validate() (err error) {
10✔
299
        if err = c.applyDefaults(); err != nil {
10✔
300
                return err
×
301
        }
×
302

303
        if !filepath.IsAbs(c.ShellCommand) {
11✔
304
                return errors.New("given shell (" + c.ShellCommand + ") is not an absolute path")
1✔
305
        }
1✔
306

307
        if !isExecutable(c.ShellCommand) {
11✔
308
                return errors.New("given shell (" + c.ShellCommand + ") is not executable")
2✔
309
        }
2✔
310

311
        err = validateUser(c)
7✔
312
        if err != nil {
11✔
313
                return err
4✔
314
        }
4✔
315

316
        if !isInShells(c.ShellCommand) {
4✔
317
                log.Errorf("ShellCommand %s is not present in /etc/shells", c.ShellCommand)
1✔
318
                return errors.New("ShellCommand " + c.ShellCommand + " is not present in /etc/shells")
1✔
319
        }
1✔
320
        log.Debugf("Verified configuration = %#v", c)
2✔
321

2✔
322
        return nil
2✔
323
}
324

325
func loadConfigFile(configFile string, config *MenderShellConfig, filesLoadedCount *int) error {
49✔
326
        // Do not treat a single config file not existing as an error here.
49✔
327
        // It is up to the caller to fail when both config files don't exist.
49✔
328
        if _, err := os.Stat(configFile); os.IsNotExist(err) {
73✔
329
                log.Debug("Configuration file does not exist: ", configFile)
24✔
330
                return nil
24✔
331
        }
24✔
332

333
        if err := readConfigFile(&config.MenderShellConfigFromFile, configFile); err != nil {
29✔
334
                log.Errorf("Error loading configuration from file: %s (%s)", configFile, err.Error())
4✔
335
                return err
4✔
336
        }
4✔
337

338
        if err := checkforDeprecatedFields(configFile); err != nil {
21✔
339
                log.Errorf("Error loading configuration from file: %s (%s)", configFile, err.Error())
×
340
                return err
×
341
        }
×
342

343
        *filesLoadedCount++
21✔
344
        log.Info("Loaded configuration file: ", configFile)
21✔
345
        return nil
21✔
346
}
347

348
func checkforDeprecatedFields(configFile string) error {
21✔
349
        var deprecatedFields MenderShellConfigDeprecated
21✔
350
        if err := readConfigFile(&deprecatedFields, configFile); err != nil {
21✔
351
                log.Errorf("Error loading configuration from file: %s (%s)", configFile, err.Error())
×
352
                return err
×
353
        }
×
354

355
        if deprecatedFields.ServerURL != "" {
23✔
356
                log.Warn("ServerURL field is deprecated, ignoring.")
2✔
357
        }
2✔
358
        if len(deprecatedFields.Servers) > 0 {
23✔
359
                log.Warn("Servers field is deprecated, ignoring.")
2✔
360
        }
2✔
361
        if deprecatedFields.ClientProtocol != "" {
22✔
362
                log.Warn("ClientProtocol field is deprecated, ignoring.")
1✔
363
        }
1✔
364
        if deprecatedFields.HTTPSClient.Certificate != "" ||
21✔
365
                deprecatedFields.HTTPSClient.Key != "" ||
21✔
366
                deprecatedFields.HTTPSClient.SSLEngine != "" {
24✔
367
                log.Warn("HTTPSClient field is deprecated, ignoring.")
3✔
368
        }
3✔
369
        if deprecatedFields.SkipVerify {
22✔
370
                log.Warn("SkipVerify field is deprecated, ignoring.")
1✔
371
        }
1✔
372
        if deprecatedFields.ServerCertificate != "" {
22✔
373
                log.Warn("ServerCertificate field is deprecated, ignoring.")
1✔
374
        }
1✔
375

376
        return nil
21✔
377
}
378

379
func readConfigFile(config interface{}, fileName string) error {
47✔
380
        // Reads mender configuration (JSON) file.
47✔
381
        log.Debug("Reading Mender configuration from file " + fileName)
47✔
382
        conf, err := ioutil.ReadFile(fileName)
47✔
383
        if err != nil {
48✔
384
                return err
1✔
385
        }
1✔
386

387
        if err := json.Unmarshal(conf, &config); err != nil {
50✔
388
                switch err.(type) {
4✔
389
                case *json.SyntaxError:
3✔
390
                        return errors.New("Error parsing mender configuration file: " + err.Error())
3✔
391
                }
392
                return errors.New("Error parsing config file: " + err.Error())
1✔
393
        }
394

395
        return nil
42✔
396
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc