-
Notifications
You must be signed in to change notification settings - Fork 1
/
tls.go
159 lines (136 loc) · 4.2 KB
/
tls.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Copyright (c) 2021 Circonus, Inc. <[email protected]>
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
package trapcheck
import (
"crypto/tls"
"crypto/x509"
"encoding/json"
"fmt"
"net/url"
"strings"
)
// clearTLSConfig sets the resetTLSConfig flag so that on the next setBrokerTLSConfig call
// the broker will be refreshed and a new tls configuration will be created. The most common
// reason for this to be done is a change to the configuration of a broker cluster (e.g. add/del).
func (tc *TrapCheck) clearTLSConfig() {
tc.resetTLSConfig = true
}
// setBrokerTLSConfig sets the broker tls configuration if was
// not supplied by the caller in the configuration.
func (tc *TrapCheck) setBrokerTLSConfig() error {
if tc.brokerList == nil {
if err := tc.initBrokerList(); err != nil {
return err
}
}
if tc.resetTLSConfig {
tc.broker = nil // force refresh
tc.tlsConfig = nil // don't use, refresh and reset
tc.resetTLSConfig = false
// tc.custTLSConfig = nil // don't use, refresh and reset
_ = tc.brokerList.RefreshBrokers()
}
// setBrokerTLSConfig has already initialized it
if tc.tlsConfig != nil {
return nil
}
u, err := url.Parse(tc.submissionURL)
if err != nil {
return fmt.Errorf("parse submission URL: %w", err)
}
if u.Scheme == "http" {
return nil // not using tls
}
// caller supplied tls config
if tc.custTLSConfig != nil {
tc.Log.Debugf("using custom tls configuration")
tc.tlsConfig = tc.custTLSConfig.Clone()
return nil
}
var public bool
public, err = tc.isPublicBroker()
if err != nil {
return err
}
if public {
return nil // public cert
}
if tc.broker == nil {
if tc.checkBundle == nil {
return fmt.Errorf("invalid state, check bundle not initialized")
}
if len(tc.checkBundle.Brokers) == 0 {
return fmt.Errorf("invalid check bundle, 0 brokers")
}
if err = tc.fetchBroker(tc.checkBundle.Brokers[0], tc.checkBundle.Type); err != nil {
return err
}
}
cn, cnList, err := tc.getBrokerCNList()
if err != nil {
return fmt.Errorf("broker cn list: %w", err)
}
certPool := x509.NewCertPool()
cert, err := tc.fetchCert()
if err != nil {
return fmt.Errorf("fetch broker ca cert: %w", err)
}
if !certPool.AppendCertsFromPEM(cert) {
return fmt.Errorf("unable to append cert to pool")
}
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12,
ServerName: cn,
// go1.15+ see VerifyConnection below - until CN added to SAN in broker certs
// NOTE: InsecureSkipVerify:true does NOT disable VerifyConnection()
InsecureSkipVerify: true, //nolint:gosec
VerifyConnection: func(cs tls.ConnectionState) error {
commonName := cs.PeerCertificates[0].Subject.CommonName
if !strings.Contains(cnList, commonName) {
tc.Log.Warnf("certificate name mismatch (refreshing TLS config) common cause, new broker added to cluster or check moved to new broker -- cn: %q, acceptable: %q", commonName, cnList)
tc.clearTLSConfig()
return x509.CertificateInvalidError{
Cert: cs.PeerCertificates[0],
Reason: x509.NameMismatch,
Detail: fmt.Sprintf("cn: %q, acceptable: %q", commonName, cnList),
}
}
opts := x509.VerifyOptions{
Roots: certPool,
Intermediates: x509.NewCertPool(),
}
for _, cert := range cs.PeerCertificates[1:] {
opts.Intermediates.AddCert(cert)
}
_, err := cs.PeerCertificates[0].Verify(opts)
if err != nil {
return fmt.Errorf("peer cert verify: %w", err)
}
return nil
},
}
tc.tlsConfig = tlsConfig
return nil
}
// caCert contains broker CA certificate returned from Circonus API.
type caCert struct {
Contents string `json:"contents"`
}
// fetchCert fetches CA certificate using Circonus API.
func (tc *TrapCheck) fetchCert() ([]byte, error) {
tc.Log.Debugf("fetching broker cert from api")
response, err := tc.client.Get("/pki/ca.crt")
if err != nil {
return nil, fmt.Errorf("fetch broker CA cert from API: %w", err)
}
cadata := new(caCert)
if err := json.Unmarshal(response, cadata); err != nil {
return nil, fmt.Errorf("json unmarshal cert: %w", err)
}
if cadata.Contents == "" {
return nil, fmt.Errorf("unable to find ca cert contents %+v", cadata)
}
return []byte(cadata.Contents), nil
}