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

mendersoftware / mender-server / 1622978334

13 Jan 2025 03:51PM UTC coverage: 72.802% (-3.8%) from 76.608%
1622978334

Pull #300

gitlab-ci

alfrunes
fix: Deployment device count should not exceed max devices

Added a condition to skip deployments when the device count reaches max
devices.

Changelog: Title
Ticket: MEN-7847
Signed-off-by: Alf-Rune Siqveland <alf.rune@northern.tech>
Pull Request #300: fix: Deployment device count should not exceed max devices

4251 of 6164 branches covered (68.96%)

Branch coverage included in aggregate %.

0 of 18 new or added lines in 1 file covered. (0.0%)

2544 existing lines in 83 files now uncovered.

42741 of 58384 relevant lines covered (73.21%)

21.49 hits per line

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

12.3
/backend/services/useradm/server.go
1
// Copyright 2023 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
package main
15

16
import (
17
        "net/http"
18
        "os"
19
        "path"
20
        "path/filepath"
21
        "regexp"
22

23
        "github.com/pkg/errors"
24

25
        "github.com/mendersoftware/mender-server/pkg/config"
26
        "github.com/mendersoftware/mender-server/pkg/log"
27

28
        api_http "github.com/mendersoftware/mender-server/services/useradm/api/http"
29
        "github.com/mendersoftware/mender-server/services/useradm/client/tenant"
30
        "github.com/mendersoftware/mender-server/services/useradm/common"
31
        . "github.com/mendersoftware/mender-server/services/useradm/config"
32
        "github.com/mendersoftware/mender-server/services/useradm/jwt"
33
        "github.com/mendersoftware/mender-server/services/useradm/store/mongo"
34
        useradm "github.com/mendersoftware/mender-server/services/useradm/user"
35
)
36

UNCOV
37
func RunServer(c config.Reader) error {
×
UNCOV
38

×
UNCOV
39
        l := log.New(log.Ctx{})
×
UNCOV
40

×
UNCOV
41
        authorizer := &SimpleAuthz{}
×
UNCOV
42

×
UNCOV
43
        // let's now go through all the existing keys and load them
×
UNCOV
44
        jwtHandlers, err := addPrivateKeys(
×
UNCOV
45
                l,
×
UNCOV
46
                filepath.Dir(c.GetString(SettingServerPrivKeyPath)),
×
UNCOV
47
                c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
48
        )
×
UNCOV
49
        if err != nil {
×
UNCOV
50
                return err
×
UNCOV
51
        }
×
52

53
        // the handler for keyId equal 0 is the one associated with the
54
        // SettingServerPrivKeyPathDefault key. it is the one serving all the previously
55
        // issued tokens (before the kid introduction in the JWTs)
UNCOV
56
        defaultHandler, err := jwt.NewJWTHandler(
×
UNCOV
57
                SettingServerPrivKeyPathDefault,
×
UNCOV
58
                c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
59
        )
×
UNCOV
60
        if err == nil && defaultHandler != nil {
×
UNCOV
61
                // the key with id 0 is by default the default one. this allows
×
UNCOV
62
                // to support tokens without "kid" in the header
×
UNCOV
63
                // it is possible, that you rotated the default key, in which case you have to
×
UNCOV
64
                // set USERADM_SERVER_PRIV_KEY_PATH=/etc/useradm/rsa/private.id.2048.pem
×
65
                // where private.id.2048.pem is the new key, with new id. the new one will by default
×
66
                // be used to issue new tokens, while any other token which has id that we have
×
67
                // will be authorized against its matching key (by id from "kid" in JWT header)
×
68
                // or which does not have "kid" will be authorized against the key with id 0.
×
69
                // in other words: the key with id 0 (if not present as private.id.0.pem)
×
70
                // is the default one, and all the JWT with no "kid" in headers are being
×
71
                // checked against it.
×
72
                jwtHandlers[common.KeyIdZero] = defaultHandler
×
73
        }
×
74

75
        // if the default path is different from the currently set key path
76
        // we still have not loaded this key. this happens when the key rotation took place,
77
        // someone exported USERADM_SERVER_PRIV_KEY_PATH=path-to-a-new-key and this key
78
        // now will serve all. if we do not have this, the Login will fall back to the keyId
79
        // from the filename and either use the KeyIdZero key or fail to find the key to issue a
80
        // token if the one set in USERADM_SERVER_PRIV_KEY_PATH does have id in the filename
81
        // (but does not exist because we have not loaded it)
82
        // this also means that careless setting of USERADM_SERVER_PRIV_KEY_PATH to a key that does
83
        // not match the SettingServerPrivKeyFileNamePattern will result in
84
        // KeyIdZero handler overwrite and lack of back support for tokens signed by it.
UNCOV
85
        if c.GetString(SettingServerPrivKeyPath) != SettingServerPrivKeyPathDefault {
×
UNCOV
86
                defaultHandler, err = jwt.NewJWTHandler(
×
UNCOV
87
                        c.GetString(SettingServerPrivKeyPath),
×
UNCOV
88
                        c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
89
                )
×
UNCOV
90
                if err == nil && defaultHandler != nil {
×
UNCOV
91
                        keyId := common.KeyIdFromPath(
×
UNCOV
92
                                c.GetString(SettingServerPrivKeyPath),
×
UNCOV
93
                                c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
94
                        )
×
UNCOV
95
                        if keyId == common.KeyIdZero {
×
UNCOV
96
                                l.Warnf(
×
UNCOV
97
                                        "currently set private key %s either does not match %s pattern"+
×
UNCOV
98
                                                " or has explicitly set id=0. we are overridding the default"+
×
UNCOV
99
                                                " private key handler with id=0",
×
UNCOV
100
                                        c.GetString(SettingServerPrivKeyPath),
×
UNCOV
101
                                        c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
102
                                )
×
UNCOV
103
                        }
×
UNCOV
104
                        jwtHandlers[keyId] = defaultHandler
×
105
                }
106
        }
107

UNCOV
108
        var jwtFallbackHandler jwt.Handler
×
UNCOV
109
        fallback := c.GetString(SettingServerFallbackPrivKeyPath)
×
UNCOV
110
        if err == nil && fallback != "" {
×
UNCOV
111
                jwtFallbackHandler, err = jwt.NewJWTHandler(
×
UNCOV
112
                        fallback,
×
UNCOV
113
                        c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
114
                )
×
115
        }
×
116
        if err != nil {
×
117
                return err
×
118
        }
×
119

UNCOV
120
        db, err := mongo.GetDataStoreMongo(dataStoreMongoConfigFromAppConfig(c))
×
121
        if err != nil {
×
122
                return errors.Wrap(err, "database connection failed")
×
UNCOV
123
        }
×
124

UNCOV
125
        ua := useradm.NewUserAdm(jwtHandlers, db,
×
126
                useradm.Config{
×
127
                        Issuer:                         c.GetString(SettingJWTIssuer),
×
UNCOV
128
                        ExpirationTimeSeconds:          int64(c.GetInt(SettingJWTExpirationTimeout)),
×
UNCOV
129
                        LimitSessionsPerUser:           c.GetInt(SettingLimitSessionsPerUser),
×
UNCOV
130
                        LimitTokensPerUser:             c.GetInt(SettingLimitTokensPerUser),
×
UNCOV
131
                        TokenLastUsedUpdateFreqMinutes: c.GetInt(SettingTokenLastUsedUpdateFreqMinutes),
×
UNCOV
132
                        PrivateKeyPath:                 c.GetString(SettingServerPrivKeyPath),
×
UNCOV
133
                        PrivateKeyFileNamePattern:      c.GetString(SettingServerPrivKeyFileNamePattern),
×
UNCOV
134
                })
×
UNCOV
135

×
UNCOV
136
        if tadmAddr := c.GetString(SettingTenantAdmAddr); tadmAddr != "" {
×
UNCOV
137
                l.Infof("settting up tenant verification")
×
UNCOV
138

×
UNCOV
139
                tc := tenant.NewClient(tenant.Config{
×
UNCOV
140
                        TenantAdmAddr: tadmAddr,
×
141
                })
×
142

×
143
                ua = ua.WithTenantVerification(tc)
×
144
        }
×
145

146
        useradmapi := api_http.NewUserAdmApiHandlers(ua, db, jwtHandlers,
×
147
                api_http.Config{
×
148
                        TokenMaxExpSeconds: c.GetInt(SettingTokenMaxExpirationSeconds),
×
UNCOV
149
                        JWTFallback:        jwtFallbackHandler,
×
UNCOV
150
                })
×
UNCOV
151

×
UNCOV
152
        handler, err := useradmapi.Build(authorizer)
×
UNCOV
153
        if err != nil {
×
UNCOV
154
                return errors.Wrap(err, "useradm API handlers setup failed")
×
UNCOV
155
        }
×
156

UNCOV
157
        addr := c.GetString(SettingListen)
×
158
        l.Printf("listening on %s", addr)
×
159

×
UNCOV
160
        return http.ListenAndServe(addr, handler)
×
161
}
162

163
func addPrivateKeys(
164
        l *log.Logger,
165
        privateKeysDirectory string,
166
        privateKeyPattern string,
167
) (handlers map[int]jwt.Handler, err error) {
1✔
168
        files, err := os.ReadDir(privateKeysDirectory)
1✔
169
        if err != nil {
1✔
UNCOV
170
                return
×
UNCOV
171
        }
×
172

173
        r, err := regexp.Compile(privateKeyPattern)
1✔
174
        if err != nil {
1✔
UNCOV
175
                return
×
UNCOV
176
        }
×
177

178
        handlers = make(map[int]jwt.Handler, len(files))
1✔
179
        for _, fileEntry := range files {
2✔
180
                if r.MatchString(fileEntry.Name()) {
2✔
181
                        keyPath := path.Join(privateKeysDirectory, fileEntry.Name())
1✔
182
                        handler, err := jwt.NewJWTHandler(keyPath, privateKeyPattern)
1✔
183
                        if err != nil {
1✔
UNCOV
184
                                continue
×
185
                        }
186
                        keyId := common.KeyIdFromPath(keyPath, privateKeyPattern)
1✔
187
                        l.Infof("loaded private key id=%d from %s", keyId, keyPath)
1✔
188
                        handlers[keyId] = handler
1✔
189
                }
190
        }
191
        return handlers, nil
1✔
192
}
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