Skip to content

Commit 5a45b63

Browse files
CopilotzouyxCopilot
authored
fix: remove automatic InsecureSkipVerify=true for HTTPS connections (CWE-295) (#359)
* fix: remove automatic InsecureSkipVerify=true for HTTPS connections (CWE-295) - Remove URL-scheme-based logic that unconditionally disabled TLS certificate verification for every HTTPS URL - Add InsecureSkipVerify bool to AppConfig and ConnectConfig as an explicit opt-in (defaults to false / secure) - Propagate the flag through ConnectConfig in abs.go, async.go, sync.go - Add a second singleton transport for the insecure case so secure and insecure connections are isolated - Fix defer res.Body.Close() inside retry loop (resource leak) - Update HTTPS unit test to explicitly set InsecureSkipVerify: true Agent-Logs-Url: https://github.com/apolloconfig/agollo/sessions/64e40f20-b4d3-4bd9-bca1-229ede403e51 Co-authored-by: zouyx <3828072+zouyx@users.noreply.github.com> * Apply suggestions from code review Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * restore url.Parse and HTTPS scheme auto-detection; read InsecureSkipVerify from config * reorder nil check before scheme check for consistency * fix: use exact scheme equality for HTTPS detection, remove unused strings import * test: add TLS verification regression coverage --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: zouyx <3828072+zouyx@users.noreply.github.com> Co-authored-by: Joe Zou <yixian.zou@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent eae8218 commit 5a45b63

7 files changed

Lines changed: 84 additions & 35 deletions

File tree

component/remote/abs.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,12 @@ func (a *AbsApolloConfig) SyncWithNamespace(namespace string, appConfigFunc func
3636
urlSuffix := a.remoteApollo.GetSyncURI(appConfig, namespace)
3737

3838
c := &env.ConnectConfig{
39-
URI: urlSuffix,
40-
AppID: appConfig.AppID,
41-
Secret: appConfig.Secret,
42-
Timeout: notifyConnectTimeout,
43-
IsRetry: true,
39+
URI: urlSuffix,
40+
AppID: appConfig.AppID,
41+
Secret: appConfig.Secret,
42+
Timeout: notifyConnectTimeout,
43+
IsRetry: true,
44+
InsecureSkipVerify: appConfig.InsecureSkipVerify,
4445
}
4546
if appConfig.SyncServerTimeout > 0 {
4647
c.Timeout = time.Duration(appConfig.SyncServerTimeout) * time.Second

component/remote/async.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,10 @@ func (a *asyncApolloConfig) notifyRemoteConfig(appConfigFunc func() config.AppCo
108108
urlSuffix := a.GetNotifyURLSuffix(notificationsMap.GetNotifies(namespace), appConfig)
109109

110110
connectConfig := &env.ConnectConfig{
111-
URI: urlSuffix,
112-
AppID: appConfig.AppID,
113-
Secret: appConfig.Secret,
111+
URI: urlSuffix,
112+
AppID: appConfig.AppID,
113+
Secret: appConfig.Secret,
114+
InsecureSkipVerify: appConfig.InsecureSkipVerify,
114115
}
115116
connectConfig.Timeout = notifyConnectTimeout
116117
notifies, err := http.RequestRecovery(appConfig, connectConfig, &http.CallBack{

component/serverlist/sync.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,9 @@ func SyncServerIPList(appConfigFunc func() config.AppConfig) (map[string]*config
108108

109109
appConfig := appConfigFunc()
110110
c := &env.ConnectConfig{
111-
AppID: appConfig.AppID,
112-
Secret: appConfig.Secret,
111+
AppID: appConfig.AppID,
112+
Secret: appConfig.Secret,
113+
InsecureSkipVerify: appConfig.InsecureSkipVerify,
113114
}
114115
if appConfig.SyncServerTimeout > 0 {
115116
c.Timeout = time.Duration(appConfig.SyncServerTimeout) * time.Second

env/config/config.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,13 @@ type AppConfig struct {
4848
Label string `json:"label"`
4949
SyncServerTimeout int `json:"syncServerTimeout"`
5050
// MustStart 可用于控制第一次同步必须成功
51-
MustStart bool `default:"false"`
52-
notificationsMap *notificationsMap
51+
MustStart bool `default:"false"`
52+
// InsecureSkipVerify controls whether the client skips TLS certificate
53+
// verification. This should only be set to true in test environments or
54+
// when connecting to a server with a self-signed certificate. Leaving it
55+
// false (the default) keeps the connection secure.
56+
InsecureSkipVerify bool `default:"false" json:"insecureSkipVerify"`
57+
notificationsMap *notificationsMap
5358
currentConnApolloConfig *CurrentApolloConfig
5459
}
5560

env/request_config.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,8 @@ type ConnectConfig struct {
3030
AppID string
3131
//密钥
3232
Secret string
33+
// InsecureSkipVerify controls whether the HTTP client skips TLS certificate
34+
// verification. Defaults to false (secure). Set to true only in test
35+
// environments or when connecting to servers with self-signed certificates.
36+
InsecureSkipVerify bool
3337
}

protocol/http/request.go

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ import (
2222
"net"
2323
"net/http"
2424
"net/url"
25-
"strings"
2625
"sync"
2726
"time"
2827

@@ -53,24 +52,40 @@ var (
5352
once sync.Once
5453
// defaultTransport http.Transport
5554
defaultTransport *http.Transport
55+
// insecureOnce for single insecure http.Transport
56+
insecureOnce sync.Once
57+
// insecureTransport is an http.Transport with TLS verification disabled.
58+
// Use only when InsecureSkipVerify is explicitly requested.
59+
insecureTransport *http.Transport
5660
)
5761

62+
func newTransport(insecureSkipVerify bool) *http.Transport {
63+
t := &http.Transport{
64+
Proxy: http.ProxyFromEnvironment,
65+
MaxIdleConns: defaultMaxConnsPerHost,
66+
MaxIdleConnsPerHost: defaultMaxConnsPerHost,
67+
DialContext: (&net.Dialer{
68+
KeepAlive: defaultKeepAliveSecond,
69+
Timeout: defaultTimeoutBySecond,
70+
}).DialContext,
71+
}
72+
if insecureSkipVerify {
73+
t.TLSClientConfig = &tls.Config{
74+
InsecureSkipVerify: true, //nolint:gosec // explicitly opt-in by caller
75+
}
76+
}
77+
return t
78+
}
79+
5880
func getDefaultTransport(insecureSkipVerify bool) *http.Transport {
81+
if insecureSkipVerify {
82+
insecureOnce.Do(func() {
83+
insecureTransport = newTransport(true)
84+
})
85+
return insecureTransport
86+
}
5987
once.Do(func() {
60-
defaultTransport = &http.Transport{
61-
Proxy: http.ProxyFromEnvironment,
62-
MaxIdleConns: defaultMaxConnsPerHost,
63-
MaxIdleConnsPerHost: defaultMaxConnsPerHost,
64-
DialContext: (&net.Dialer{
65-
KeepAlive: defaultKeepAliveSecond,
66-
Timeout: defaultTimeoutBySecond,
67-
}).DialContext,
68-
}
69-
if insecureSkipVerify {
70-
defaultTransport.TLSClientConfig = &tls.Config{
71-
InsecureSkipVerify: insecureSkipVerify,
72-
}
73-
}
88+
defaultTransport = newTransport(false)
7489
})
7590
return defaultTransport
7691
}
@@ -92,15 +107,14 @@ func Request(requestURL string, connectionConfig *env.ConnectConfig, callBack *C
92107
} else {
93108
client.Timeout = connectTimeout
94109
}
95-
var err error
96110
u, err := url.Parse(requestURL)
97111
if err != nil {
98112
log.Errorf("request Apollo Server url: %q is invalid: %v", requestURL, err)
99113
return nil, err
100114
}
101115
var insecureSkipVerify bool
102-
if strings.HasPrefix(u.Scheme, "https") {
103-
insecureSkipVerify = true
116+
if connectionConfig != nil && u.Scheme == "https" {
117+
insecureSkipVerify = connectionConfig.InsecureSkipVerify
104118
}
105119
client.Transport = getDefaultTransport(insecureSkipVerify)
106120
retry := 0
@@ -138,11 +152,11 @@ func Request(requestURL string, connectionConfig *env.ConnectConfig, callBack *C
138152

139153
var res *http.Response
140154
res, err = client.Do(req)
141-
if res != nil {
142-
defer res.Body.Close()
143-
}
144155

145156
if res == nil || err != nil {
157+
if res != nil {
158+
res.Body.Close()
159+
}
146160
log.Errorf("Connect Apollo Server Fail, url:%s, error:%v", requestURL, err)
147161
// if error then sleep
148162
time.Sleep(onErrorRetryInterval)
@@ -154,6 +168,7 @@ func Request(requestURL string, connectionConfig *env.ConnectConfig, callBack *C
154168
case http.StatusOK:
155169
var responseBody []byte
156170
responseBody, err = io.ReadAll(res.Body)
171+
res.Body.Close()
157172
if err != nil {
158173
log.Errorf("Connect Apollo Server Fail, url: %s , error: %v", requestURL, err)
159174
// if error then sleep
@@ -166,15 +181,18 @@ func Request(requestURL string, connectionConfig *env.ConnectConfig, callBack *C
166181
}
167182
return nil, nil
168183
case http.StatusNotModified:
184+
res.Body.Close()
169185
log.Debugf("Config Not Modified, error: %v", err)
170186
if callBack != nil && callBack.NotModifyCallBack != nil {
171187
return nil, callBack.NotModifyCallBack()
172188
}
173189
return nil, nil
174190
case http.StatusBadRequest, http.StatusUnauthorized, http.StatusNotFound, http.StatusMethodNotAllowed:
191+
res.Body.Close()
175192
log.Errorf("Connect Apollo Server Fail, url:%s, StatusCode:%d", requestURL, res.StatusCode)
176193
return nil, errors.New(fmt.Sprintf("Connect Apollo Server Fail, StatusCode:%d", res.StatusCode))
177194
default:
195+
res.Body.Close()
178196
log.Errorf("Connect Apollo Server Fail, url:%s, StatusCode:%d", requestURL, res.StatusCode)
179197
// if error then sleep
180198
time.Sleep(onErrorRetryInterval)

protocol/http/request_test.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
json2 "encoding/json"
1919
"fmt"
2020
"net/http"
21+
"net/http/httptest"
2122
"net/url"
2223
"testing"
2324
"time"
@@ -62,15 +63,17 @@ func TestHttpsRequestRecovery(t *testing.T) {
6263
server := runNormalBackupConfigResponseWithHTTPS()
6364
appConfig := getTestAppConfig()
6465
appConfig.IP = server.URL
66+
appConfig.InsecureSkipVerify = true
6567

6668
mockIPList(t, func() config.AppConfig {
6769
return *appConfig
6870
})
6971
urlSuffix := getConfigURLSuffix(appConfig, appConfig.NamespaceName)
7072

7173
o, err := RequestRecovery(*appConfig, &env.ConnectConfig{
72-
URI: urlSuffix,
73-
IsRetry: true,
74+
URI: urlSuffix,
75+
IsRetry: true,
76+
InsecureSkipVerify: true,
7477
}, &CallBack{
7578
SuccessCallBack: nil,
7679
})
@@ -79,6 +82,22 @@ func TestHttpsRequestRecovery(t *testing.T) {
7982
Assert(t, o, NilVal())
8083
}
8184

85+
func TestTLSVerificationDefaultsToEnabled(t *testing.T) {
86+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
87+
w.WriteHeader(http.StatusOK)
88+
}))
89+
defer server.Close()
90+
91+
_, err := Request(server.URL, &env.ConnectConfig{IsRetry: false}, nil)
92+
Assert(t, err, NotNilVal())
93+
94+
_, err = Request(server.URL, &env.ConnectConfig{
95+
IsRetry: false,
96+
InsecureSkipVerify: true,
97+
}, nil)
98+
Assert(t, err, NilVal())
99+
}
100+
82101
func TestRequestRecovery(t *testing.T) {
83102
time.Sleep(1 * time.Second)
84103
server := runNormalBackupConfigResponse()

0 commit comments

Comments
 (0)