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

mendersoftware / mender-server / 1702610555

06 Mar 2025 09:22AM UTC coverage: 65.502% (+0.02%) from 65.481%
1702610555

Pull #416

gitlab-ci

mzedel
test: added e2e tests for webhook functionality

Ticket: MEN-7926
Signed-off-by: Manuel Zedel <manuel.zedel@northern.tech>
Pull Request #416: MEN-7926 + MEN-8077 - webhook e2e tests + configurable webhook address validation

8 of 12 new or added lines in 2 files covered. (66.67%)

3 existing lines in 2 files now uncovered.

31661 of 48336 relevant lines covered (65.5%)

1.38 hits per line

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

97.73
/backend/services/iot-manager/client/client.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 client
16

17
import (
18
        "bytes"
19
        "context"
20
        "crypto/hmac"
21
        "crypto/sha256"
22
        "crypto/tls"
23
        "encoding/hex"
24
        "encoding/json"
25
        "errors"
26
        "net"
27
        "net/http"
28
        "sync"
29
        "syscall"
30
        "time"
31

32
        "github.com/mendersoftware/mender-server/pkg/config"
33
        dconfig "github.com/mendersoftware/mender-server/services/iot-manager/config"
34
        inet "github.com/mendersoftware/mender-server/services/iot-manager/internal/net"
35
        "github.com/mendersoftware/mender-server/services/iot-manager/model"
36
)
37

38
const (
39
        ParamAlgorithmType = "X-Men-Algorithm"
40
        ParamSignature     = "X-Men-Signature"
41

42
        HdrKeyContentType    = "Content-Type"
43
        AlgorithmTypeHMAC256 = "MEN-HMAC-SHA256-Payload"
44
)
45

46
var (
47
        skipVerify         bool
48
        skipVerifyLoadOnce sync.Once
49
)
50

51
func New() *http.Client {
3✔
52
        return &http.Client{
3✔
53
                Transport: NewTransport(),
3✔
54
                CheckRedirect: func(req *http.Request, via []*http.Request) error {
4✔
55
                        return http.ErrUseLastResponse
1✔
56
                },
1✔
57
        }
58
}
59

60
func addrIsGlobalUnicast(network, address string, _ syscall.RawConn) error {
3✔
61
        skipVerifyLoadOnce.Do(func() {
6✔
62
                skipVerify = config.Config.GetBool(dconfig.SettingDomainSkipVerify)
3✔
63
        })
3✔
64
        if skipVerify {
3✔
NEW
65
                return nil
×
NEW
66
        }
×
67
        ipAddr, _, err := net.SplitHostPort(address)
3✔
68
        if err != nil {
4✔
69
                ipAddr = address
1✔
70
        }
1✔
71
        ip := net.ParseIP(ipAddr)
3✔
72
        if ip == nil {
4✔
73
                return &net.ParseError{
1✔
74
                        Type: "IP address",
1✔
75
                        Text: address,
1✔
76
                }
1✔
77
        } else if !inet.IsGlobalUnicast(ip) {
5✔
78
                return net.InvalidAddrError("destination address is in reserved address range")
1✔
79
        }
1✔
80
        return nil
3✔
81
}
82

83
func NewTransport() http.RoundTripper {
3✔
84
        dialer := &net.Dialer{
3✔
85
                Control: addrIsGlobalUnicast,
3✔
86
        }
3✔
87
        tlsDialer := &tls.Dialer{
3✔
88
                NetDialer: dialer,
3✔
89
        }
3✔
90
        return &http.Transport{
3✔
91
                Proxy:                 nil,
3✔
92
                DialContext:           dialer.DialContext,
3✔
93
                DialTLSContext:        tlsDialer.DialContext,
3✔
94
                ForceAttemptHTTP2:     true,
3✔
95
                MaxIdleConns:          100,
3✔
96
                IdleConnTimeout:       90 * time.Second,
3✔
97
                TLSHandshakeTimeout:   10 * time.Second,
3✔
98
                ExpectContinueTimeout: 1 * time.Second,
3✔
99
        }
3✔
100
}
3✔
101

102
// NewSignedRequest appends header X-Men-Signature with value:
103
// HMAC256(Request.Body, secret)
104
func NewSignedRequest(
105
        ctx context.Context,
106
        secret []byte,
107
        method string,
108
        url string,
109
        body []byte,
110
) (*http.Request, error) {
2✔
111
        req, err := http.NewRequestWithContext(ctx, method, url, bytes.NewReader(body))
2✔
112
        if err != nil {
3✔
113
                return nil, err
1✔
114
        }
1✔
115
        req.Header.Set(ParamAlgorithmType, AlgorithmTypeHMAC256)
2✔
116

2✔
117
        sign := hmac.New(sha256.New, secret)
2✔
118
        _, _ = sign.Write(body) // Writer cannot error
2✔
119

2✔
120
        req.Header.Set(ParamSignature, hex.EncodeToString(sign.Sum(nil)))
2✔
121

2✔
122
        return req, nil
2✔
123
}
124

125
func NewWebhookRequest(
126
        ctx context.Context,
127
        creds *model.Credentials,
128
        event model.WebhookEvent,
129
) (*http.Request, error) {
3✔
130
        err := creds.Validate()
3✔
131
        if err != nil {
4✔
132
                return nil, err
1✔
133
        } else if creds.Type != model.CredentialTypeHTTP {
5✔
134
                return nil, errors.New("invalid credentials for webhooks")
1✔
135
        }
1✔
136

137
        b, err := json.Marshal(event)
3✔
138
        if err != nil {
4✔
139
                return nil, err
1✔
140
        }
1✔
141
        var req *http.Request
3✔
142
        if creds.HTTP.Secret != nil {
5✔
143
                req, err = NewSignedRequest(
2✔
144
                        ctx,
2✔
145
                        []byte(*creds.HTTP.Secret),
2✔
146
                        http.MethodPost,
2✔
147
                        creds.HTTP.URL,
2✔
148
                        b,
2✔
149
                )
2✔
150
        } else {
4✔
151
                req, err = http.NewRequestWithContext(
2✔
152
                        ctx, http.MethodPost,
2✔
153
                        creds.HTTP.URL, bytes.NewReader(b),
2✔
154
                )
2✔
155
        }
2✔
156
        if err == nil {
6✔
157
                req.Header.Set(HdrKeyContentType, "application/json")
3✔
158
        }
3✔
159
        return req, err
3✔
160
}
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