Skip to content

Commit 6456d8b

Browse files
authored
🐛 detect if tls is enabled (#5531)
The `tls` resource is part of multiple core providers, it requires a `socket` to know the host, port and protocol, many fields of this resource require that the server (configured in the socket) speaks TLS and not plain text (like HTTP), therefore this change makes it so that we only detect once if the socket is running on TLS or not. If not, we return null properly to indicate that there are no certificates, if it does, then we collect TLS information. Additionally, I am setting a default timeout of one minute to dial to the socket compared to what was before (unlimited=no timeout). Finally, if the TLS tester returns a timeout, we don't error the detection but instead, we set the fields as null. Signed-off-by: Salim Afiune Maya <afiune@mondoo.com>
1 parent d2deeca commit 6456d8b

File tree

5 files changed

+148
-33
lines changed

5 files changed

+148
-33
lines changed

providers/network/resources/socket.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33

44
package resources
55

6-
import "strconv"
6+
import (
7+
"net"
8+
"strconv"
9+
)
710

811
func (s *mqlSocket) id() (string, error) {
9-
return s.Protocol.Data + "://" + s.Address.Data + ":" + strconv.Itoa(int(s.Port.Data)), nil
12+
return s.Protocol.Data + "://" + net.JoinHostPort(s.Address.Data, strconv.Itoa(int(s.Port.Data))), nil
1013
}

providers/network/resources/tls.go

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ import (
1313
"time"
1414

1515
"github.com/cockroachdb/errors"
16+
"github.com/rs/zerolog/log"
1617
"go.mondoo.com/cnquery/v11/llx"
1718
"go.mondoo.com/cnquery/v11/providers-sdk/v1/plugin"
19+
"go.mondoo.com/cnquery/v11/providers-sdk/v1/util/convert"
1820
"go.mondoo.com/cnquery/v11/providers/core/resources/regex"
1921
"go.mondoo.com/cnquery/v11/providers/network/connection"
2022
"go.mondoo.com/cnquery/v11/providers/network/resources/certificates"
@@ -26,6 +28,8 @@ var reTarget = regexp.MustCompile("([^/:]+?)(:\\d+)?$")
2628

2729
var rexUrlDomain = regexp.MustCompile(regex.UrlDomain)
2830

31+
var DefaultDialerTimeout = time.Minute * 1
32+
2933
// Returns the connection's port adjusted for TLS.
3034
// If no port is set, we estimate what it might be from the scheme.
3135
// If that doesn't help, we set it to 443.
@@ -108,7 +112,15 @@ func initTls(runtime *plugin.Runtime, args map[string]*llx.RawData) (map[string]
108112
}
109113

110114
type mqlTlsInternal struct {
115+
// This mutex is used to protect the tls resource from doing multiple dections at once
111116
lock sync.Mutex
117+
// we only detect once if the a socket is running on TLS or not, once the detection runs,
118+
// this boolean gets set and tells other functions if the socket has tls enabled or not
119+
tlsEnabled *bool
120+
// if the socket has TLS enabled, this tester will have findings, ciphers and versions
121+
tester *tlsshake.Tester
122+
// during TLS detection, if we find any issue, we record it here
123+
Error error
112124
}
113125

114126
func (s *mqlTls) id() (string, error) {
@@ -185,24 +197,20 @@ func parseCertificates(runtime *plugin.Runtime, domainName string, certificateLi
185197
}
186198

187199
func (s *mqlTls) params(socket *mqlSocket, domainName string) (map[string]interface{}, error) {
188-
s.lock.Lock()
189-
defer s.lock.Unlock()
190-
191-
host := socket.Address.Data
192-
port := socket.Port.Data
193-
proto := socket.Protocol.Data
194-
195-
tester := tlsshake.New(proto, domainName, host, int(port))
196-
if err := tester.Test(tlsshake.DefaultScanConfig()); err != nil {
197-
if errors.Is(err, tlsshake.ErrFailedToConnect) || errors.Is(err, tlsshake.ErrFailedToTlsResponse) {
198-
s.Params.State = plugin.StateIsSet | plugin.StateIsNull
199-
return nil, nil
200-
}
200+
enabled, err := s.TLSEnabled(socket, domainName)
201+
if err != nil {
201202
return nil, err
202203
}
203204

205+
if !enabled {
206+
return nil, nil
207+
}
208+
209+
s.lock.Lock()
210+
defer s.lock.Unlock()
211+
204212
res := map[string]interface{}{}
205-
findings := tester.Findings
213+
findings := s.tester.Findings
206214

207215
lists := map[string][]string{
208216
"errors": findings.Errors,
@@ -303,9 +311,16 @@ func (s *mqlTls) extensions(params interface{}) ([]interface{}, error) {
303311
return res, nil
304312
}
305313

306-
func gatherTlsCertificates(host, port, domainName string) ([]*x509.Certificate, []*x509.Certificate, error) {
314+
func gatherTlsCertificates(proto, host, port, domainName string) ([]*x509.Certificate, []*x509.Certificate, error) {
307315
isSNIcert := map[string]struct{}{}
308-
conn, err := tls.Dial("tcp", net.JoinHostPort(host, port), &tls.Config{
316+
dialer := &net.Dialer{Timeout: DefaultDialerTimeout}
317+
addr := net.JoinHostPort(host, port)
318+
log.Trace().
319+
Str("address", addr).
320+
Str("domain_name", domainName).
321+
Dur("timeout", DefaultDialerTimeout).
322+
Msg("network.tls> gathering tls certificates")
323+
conn, err := tls.DialWithDialer(dialer, proto, addr, &tls.Config{
309324
InsecureSkipVerify: true,
310325
ServerName: domainName,
311326
})
@@ -321,7 +336,7 @@ func gatherTlsCertificates(host, port, domainName string) ([]*x509.Certificate,
321336
}
322337

323338
nonSniCerts := []*x509.Certificate{}
324-
nonSniConn, err := tls.Dial("tcp", net.JoinHostPort(host, port), &tls.Config{
339+
nonSniConn, err := tls.DialWithDialer(dialer, proto, addr, &tls.Config{
325340
InsecureSkipVerify: true,
326341
})
327342
if err != nil {
@@ -338,11 +353,93 @@ func gatherTlsCertificates(host, port, domainName string) ([]*x509.Certificate,
338353
return sniCerts, nonSniCerts, nil
339354
}
340355

356+
// we should only detect once if the socket is running on TLS or not, if we have already detected it and, it
357+
// is NOT a TLS connection, we should exit fast.
358+
//
359+
// NOTE that this method should be called by functions once they have locked the Mutex inside `mqlTlsInternal`
360+
func (s *mqlTls) unsafeTLSTest(socket *mqlSocket, domainName string) error {
361+
if s.tlsEnabled != nil {
362+
return s.Error
363+
}
364+
365+
host := socket.Address.Data
366+
port := socket.Port.Data
367+
proto := socket.Protocol.Data
368+
369+
s.tester = tlsshake.New(proto, domainName, host, int(port))
370+
if err := s.tester.Test(tlsshake.DefaultScanConfig()); err != nil {
371+
372+
log.Debug().
373+
Str("host", host).
374+
Str("proto", proto).
375+
Int64("port", port).
376+
Interface("findings", s.tester.Findings).
377+
Bool("tls_enabled", false).
378+
Msg("network.tls> detection")
379+
s.tlsEnabled = convert.ToPtr(false)
380+
381+
if errors.Is(err, tlsshake.ErrFailedToConnect) ||
382+
errors.Is(err, tlsshake.ErrFailedToWrite) ||
383+
errors.Is(err, tlsshake.ErrTimeout) ||
384+
errors.Is(err, tlsshake.ErrFailedToTlsResponse) {
385+
386+
s.Params.State = plugin.StateIsSet | plugin.StateIsNull
387+
s.Certificates.State = plugin.StateIsSet | plugin.StateIsNull
388+
s.NonSniCertificates.State = plugin.StateIsSet | plugin.StateIsNull
389+
return nil
390+
}
391+
392+
s.Error = err
393+
return s.Error
394+
}
395+
396+
s.tlsEnabled = convert.ToPtr(len(s.tester.Findings.Versions) != 0)
397+
398+
log.Debug().
399+
Str("host", host).
400+
Str("proto", proto).
401+
Int64("port", port).
402+
Strs("versions", s.tester.Findings.Errors).
403+
Interface("versions", s.tester.Findings.Versions).
404+
Bool("tls_enabled", *s.tlsEnabled).
405+
Msg("network.tls> detection")
406+
return nil
407+
}
408+
409+
// TLSEnabled checks if the provider socket speaks TLS or plain text (like HTTP)
410+
func (s *mqlTls) TLSEnabled(socket *mqlSocket, domainName string) (enabled bool, err error) {
411+
s.lock.Lock()
412+
defer s.lock.Unlock()
413+
414+
if s.tlsEnabled == nil {
415+
// we only detect once
416+
err = s.unsafeTLSTest(socket, domainName)
417+
} else {
418+
err = s.Error
419+
}
420+
421+
enabled = *s.tlsEnabled
422+
423+
return
424+
}
341425
func (s *mqlTls) populateCertificates(socket *mqlSocket, domainName string) error {
426+
enabled, err := s.TLSEnabled(socket, domainName)
427+
if err != nil {
428+
return err
429+
}
430+
431+
if !enabled {
432+
return nil
433+
}
434+
435+
s.lock.Lock()
436+
defer s.lock.Unlock()
437+
342438
host := socket.Address.Data
343439
port := socket.Port.Data
440+
proto := socket.Protocol.Data
344441

345-
certs, nonSniCerts, err := gatherTlsCertificates(host, strconv.FormatInt(port, 10), domainName)
442+
certs, nonSniCerts, err := gatherTlsCertificates(proto, host, strconv.FormatInt(port, 10), domainName)
346443
if err != nil {
347444
s.Certificates = plugin.TValue[[]interface{}]{Error: err, State: plugin.StateIsSet}
348445
s.NonSniCertificates = plugin.TValue[[]interface{}]{Error: err, State: plugin.StateIsSet}

providers/network/resources/tlsshake/tlsshake.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ const DefaultTimeout = 2 * time.Second
4646

4747
var ErrFailedToConnect = errors.New("failed to connect")
4848

49+
var ErrFailedToWrite = errors.New("failed to write")
50+
4951
var ErrFailedToTlsResponse = errors.New("failed to get a TLS response")
5052

53+
var ErrTimeout = errors.New("timeout failure")
54+
5155
func DefaultScanConfig() ScanConfig {
5256
return ScanConfig{
5357
SNIsupported: true,
@@ -247,7 +251,7 @@ func (s *Tester) testTLSWithConn(conn net.Conn, conf *ScanConfig) (int, error) {
247251

248252
_, err = conn.Write(msg)
249253
if err != nil {
250-
return 0, multierr.Wrap(err, "failed to send TLS hello")
254+
return 0, ErrFailedToWrite
251255
}
252256

253257
success, err := s.parseHello(conn, conf)
@@ -545,6 +549,8 @@ func (s *Tester) parseHello(conn net.Conn, conf *ScanConfig) (bool, error) {
545549
s.sync.Unlock()
546550
}
547551
return false, nil
552+
} else if strings.Contains(err.Error(), "i/o timeout") {
553+
return false, ErrTimeout
548554
}
549555
return false, err
550556
}

providers/os/resources/lsof/lsof.go

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ package lsof
55

66
import (
77
"bufio"
8-
"errors"
98
"fmt"
109
"io"
10+
"net"
1111
"regexp"
1212
"strconv"
1313
"strings"
14+
15+
"github.com/cockroachdb/errors"
1416
)
1517

1618
func init() {
@@ -113,7 +115,6 @@ type FileDescriptor struct {
113115

114116
// n127.0.0.1:3000->127.0.0.1:54335
115117
var networkNameRegexp = regexp.MustCompile(`(.*):(.*)->(.*):(.*)`)
116-
var networkLoopbackRegexp = regexp.MustCompile(`(.*):(.*)`)
117118

118119
// local and remote Internet addresses of a network file
119120
func (f *FileDescriptor) NetworkFile() (string, int64, string, int64, error) {
@@ -144,20 +145,20 @@ func (f *FileDescriptor) NetworkFile() (string, int64, string, int64, error) {
144145
}
145146

146147
// loop-back address [::1]:17223 or *:56863
147-
address := networkLoopbackRegexp.FindStringSubmatch(f.Name)
148-
if len(address) < 3 {
149-
return "", 0, "", 0, errors.New("network name not supported: " + f.Name)
148+
host, port, err := net.SplitHostPort(f.Name)
149+
if err != nil {
150+
return "", 0, "", 0, errors.Wrapf(err, "network name not supported: %s", f.Name)
150151
}
152+
151153
localPort := 0
152-
var err error
153-
if address[2] != "*" {
154-
localPort, err = strconv.Atoi(address[2])
154+
if port != "*" {
155+
localPort, err = strconv.Atoi(port)
155156
if err != nil {
156157
return "", 0, "", 0, errors.New("network name not supported: " + f.Name)
157158
}
158159
}
159160

160-
return address[1], int64(localPort), "", 0, nil
161+
return host, int64(localPort), "", 0, nil
161162
}
162163

163164
// maps lsof state to tcp states

providers/os/resources/port.go

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -602,8 +602,12 @@ func (p *mqlPorts) listMacos() ([]interface{}, error) {
602602
}
603603
// lsof presents a process listening on any ipv6 address as listening on "*"
604604
// change this to a more ipv6-friendly formatting
605-
if protocol == "ipv6" && strings.HasPrefix(localAddress, "*") {
606-
localAddress = strings.Replace(localAddress, "*", "[::]", 1)
605+
if strings.HasPrefix(localAddress, "*") {
606+
if strings.HasSuffix(protocol, "6") {
607+
localAddress = strings.Replace(localAddress, "*", "::1", 1)
608+
} else {
609+
localAddress = strings.Replace(localAddress, "*", "127.0.0.1", 1)
610+
}
607611
}
608612

609613
state, ok := TCP_STATES[fd.TcpState()]
@@ -649,7 +653,11 @@ func (s *mqlPort) id() (string, error) {
649653

650654
func (s *mqlPort) tls(address string, port int64, proto string) (plugin.Resource, error) {
651655
if address == "" || address == "0.0.0.0" {
652-
address = "127.0.0.1"
656+
if proto == "tcp6" {
657+
address = "::1"
658+
} else {
659+
address = "127.0.0.1"
660+
}
653661
}
654662

655663
socket, err := s.MqlRuntime.CreateSharedResource("socket", map[string]*llx.RawData{

0 commit comments

Comments
 (0)