Skip to content

Commit 23b6f26

Browse files
author
OpenShift Bot
authored
Merge pull request #11145 from fabianofranz/issues_11122
Merged by openshift-bot
2 parents b6ba8ec + 34a5bfd commit 23b6f26

File tree

4 files changed

+266
-4
lines changed

4 files changed

+266
-4
lines changed

pkg/cmd/cli/cmd/login/helpers.go

+16-2
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package login
22

33
import (
44
"bytes"
5+
"crypto/x509"
56
"fmt"
67
"io"
78
"net"
@@ -82,10 +83,23 @@ func dialToServer(clientConfig restclient.Config) error {
8283
return nil
8384
}
8485

85-
func promptForInsecureTLS(reader io.Reader, out io.Writer) bool {
86+
func promptForInsecureTLS(reader io.Reader, out io.Writer, reason error) bool {
87+
var insecureTLSRequestReason string
88+
if reason != nil {
89+
switch reason.(type) {
90+
case x509.UnknownAuthorityError:
91+
insecureTLSRequestReason = "The server uses a certificate signed by an unknown authority."
92+
case x509.HostnameError:
93+
insecureTLSRequestReason = fmt.Sprintf("The server is using a certificate that does not match its hostname: %s", reason.Error())
94+
case x509.CertificateInvalidError:
95+
insecureTLSRequestReason = fmt.Sprintf("The server is using an invalid certificate: %s", reason.Error())
96+
}
97+
}
8698
var input bool
8799
if term.IsTerminal(reader) {
88-
fmt.Fprintln(out, "The server uses a certificate signed by an unknown authority.")
100+
if len(insecureTLSRequestReason) > 0 {
101+
fmt.Fprintln(out, insecureTLSRequestReason)
102+
}
89103
fmt.Fprintln(out, "You can bypass the certificate check, but any data you send to the server could be intercepted by others.")
90104
input = cmdutil.PromptForBool(os.Stdin, out, "Use insecure connections? (y/n): ")
91105
fmt.Fprintln(out)

pkg/cmd/cli/cmd/login/loginoptions.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,10 @@ func (o *LoginOptions) getClientConfig() (*restclient.Config, error) {
121121
// certificate authority unknown, check or prompt if we want an insecure
122122
// connection or if we already have a cluster stanza that tells us to
123123
// connect to this particular server insecurely
124-
case x509.UnknownAuthorityError:
124+
case x509.UnknownAuthorityError, x509.HostnameError, x509.CertificateInvalidError:
125125
if o.InsecureTLS ||
126126
hasExistingInsecureCluster(*clientConfig, *o.StartingKubeConfig) ||
127-
promptForInsecureTLS(o.Reader, o.Out) {
127+
promptForInsecureTLS(o.Reader, o.Out, err) {
128128
clientConfig.Insecure = true
129129
clientConfig.CAFile = ""
130130
clientConfig.CAData = nil

pkg/cmd/cli/cmd/login/loginoptions_test.go

+220
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package login
22

33
import (
4+
"crypto/tls"
5+
"fmt"
46
"net/http"
57
"net/http/httptest"
8+
"regexp"
69
"strings"
710
"testing"
811

12+
"github.com/MakeNowJust/heredoc"
13+
914
"github.com/openshift/origin/pkg/cmd/cli/config"
1015
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
1116

1217
"k8s.io/kubernetes/pkg/client/restclient"
18+
kclientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
1319
)
1420

1521
func TestNormalizeServerURL(t *testing.T) {
@@ -55,6 +61,163 @@ func TestNormalizeServerURL(t *testing.T) {
5561
}
5662
}
5763

64+
func TestTLSWithCertificateNotMatchingHostname(t *testing.T) {
65+
// generated by 'go run src/crypto/tls/generate_cert.go --rsa-bits 1024 --host invalidhost.com,8.8.8.8 --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h'
66+
invalidHostCert := heredoc.Doc(`
67+
-----BEGIN CERTIFICATE-----
68+
MIICBjCCAW+gAwIBAgIRALOIWXyeLzunaiVkP2itHAEwDQYJKoZIhvcNAQELBQAw
69+
EjEQMA4GA1UEChMHQWNtZSBDbzAgFw03MDAxMDEwMDAwMDBaGA8yMDg0MDEyOTE2
70+
MDAwMFowEjEQMA4GA1UEChMHQWNtZSBDbzCBnzANBgkqhkiG9w0BAQEFAAOBjQAw
71+
gYkCgYEAuKDlC4aMBbHaXgS+MFud5h3zeE4boSqKgFI6HceySF/a+qg0v+ID6EwQ
72+
DpJ2W5AdJGEBfixo+tym6q3oKWHJUX0hInkJ6dXIdUbVOeO5dIsGG0fZmRD7DDDx
73+
snkXrDB/E0JglHNckRbIh/jvznbDfbddIcdgZ7JVIfnNpigtHZECAwEAAaNaMFgw
74+
DgYDVR0PAQH/BAQDAgKkMBMGA1UdJQQMMAoGCCsGAQUFBwMBMA8GA1UdEwEB/wQF
75+
MAMBAf8wIAYDVR0RBBkwF4IPaW52YWxpZGhvc3QuY29thwQICAgIMA0GCSqGSIb3
76+
DQEBCwUAA4GBAAkPU044aFkBl4f/muwSh/oPGinnA4fp8ei0KMnLk+0/CjNb3Waa
77+
GtuRVIudRTK2M/RzdpUrwfWlVmkezV4BR1K/aOH9a29zqDTkEjnkIbWwe+piAs+w
78+
VxIxrTqM8rqq8qxeWS54AyF/OaLJgXzDpCFnCb7kY3iyHv6lcmCjluLW
79+
-----END CERTIFICATE-----`)
80+
invalidHostKey := heredoc.Doc(`
81+
-----BEGIN RSA PRIVATE KEY-----
82+
MIICXQIBAAKBgQC4oOULhowFsdpeBL4wW53mHfN4ThuhKoqAUjodx7JIX9r6qDS/
83+
4gPoTBAOknZbkB0kYQF+LGj63KbqregpYclRfSEieQnp1ch1RtU547l0iwYbR9mZ
84+
EPsMMPGyeResMH8TQmCUc1yRFsiH+O/OdsN9t10hx2BnslUh+c2mKC0dkQIDAQAB
85+
AoGAZ0ZAuNC7NFhHEL5QcJZe3aC1Vv9B/0XfkWXtckkJFejggcNjNk5D50Xc2Xnd
86+
0NvtITNN9Xj8BA83IyDCM5uqUwDbOLIc6qYgAGWzxZZSDAQg1iOAAZoXmMTNS6Zf
87+
hQhNUIwB68ELGvbcq7cxQL7L9n4GfISz7PKOOUKTZp0Q8G0CQQD07K7NES340c3I
88+
QVkCW5/ygNK0GuQ8nTcG5yC8R5SDS47N8YzPp17Pajah8+wawYiemY1fUmD7P/bq
89+
Cjl2RtIHAkEAwPo1GzJubN7PSYgPir3TxUGtMJoyc3jfdjblXyGJHwTu2YxeRjd2
90+
YUPVRpu9JvNjZc+GONvTbTZeNWCvy0JNpwJBAKEsi49JCd6eefOZBTDnCKd1nLKG
91+
q8Ezl/2D5WfhFtsbwrrFhOs1cc++Tnte3/VvfC8aTwz2UfmkyyCSX+P0kMsCQCIL
92+
glb7/LNEU7mbQXKurq+8OHu8mG36wyGt6aVw2yoXyrOiqfclTcM3HmdIjoRSqBSM
93+
Ghfp4FECKHiuSBVJ6z0CQQDF37CRpdQRDPnAedhyApLcIxSbYo1oUm7FxBLyVb7V
94+
HQjFvsOylsSCABXz0FyC7zXQxkEo6CiSahVI/PHz6Zta
95+
-----END RSA PRIVATE KEY-----`)
96+
97+
server, err := newTLSServer(invalidHostCert, invalidHostKey)
98+
if err != nil {
99+
t.Errorf(err.Error())
100+
}
101+
server.StartTLS()
102+
defer server.Close()
103+
104+
testCases := map[string]struct {
105+
serverURL string
106+
skipTLSVerify bool
107+
expectedErrMsg *regexp.Regexp
108+
}{
109+
"succeed skipping tls": {
110+
serverURL: server.URL,
111+
skipTLSVerify: true,
112+
},
113+
"certificate hostname doesn't match": {
114+
serverURL: server.URL,
115+
expectedErrMsg: regexp.MustCompile(`The server is using a certificate that does not match its hostname(.*)is valid for 8\.8\.8\.8`),
116+
},
117+
}
118+
119+
for name, test := range testCases {
120+
t.Logf("evaluating test: %s", name)
121+
options := &LoginOptions{
122+
Server: test.serverURL,
123+
InsecureTLS: test.skipTLSVerify,
124+
StartingKubeConfig: &kclientcmdapi.Config{},
125+
}
126+
127+
if _, err = options.getClientConfig(); err != nil {
128+
if !test.expectedErrMsg.MatchString(err.Error()) {
129+
t.Errorf("%s: expected error %q but got %q", name, test.expectedErrMsg, err)
130+
}
131+
if test.expectedErrMsg == nil {
132+
t.Errorf("%s: unexpected error: %v", name, err)
133+
}
134+
} else {
135+
if test.expectedErrMsg != nil {
136+
t.Errorf("%s: expected error but got nothing", name)
137+
}
138+
}
139+
}
140+
}
141+
142+
func TestTLSWithExpiredCertificate(t *testing.T) {
143+
// generated by 'go run src/crypto/tls/generate_cert.go --rsa-bits 1024 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1h'
144+
expiredCert := heredoc.Doc(`
145+
-----BEGIN CERTIFICATE-----
146+
MIICEjCCAXugAwIBAgIRALf82bYpro/jQS8fP74dG5EwDQYJKoZIhvcNAQELBQAw
147+
EjEQMA4GA1UEChMHQWNtZSBDbzAeFw03MDAxMDEwMDAwMDBaFw03MDAxMDEwMTAw
148+
MDBaMBIxEDAOBgNVBAoTB0FjbWUgQ28wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
149+
AoGBAONNgDXBk2Q1i/aJjTwt03KpQ3nQblMS3IX/H9JWw6ta6UublKBOaD/2o5Xt
150+
FM+Q7XDEnzYw88CK5KHdyejkJo5IBpUjQYJZFzUJ1BC8Lw7yy6dXWYBJboRR1S+1
151+
JhkMJOtpPecv+4cTaynplYj0WMBjcQthg2RM7tdpyUYpsp2rAgMBAAGjaDBmMA4G
152+
A1UdDwEB/wQEAwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTAD
153+
AQH/MC4GA1UdEQQnMCWCC2V4YW1wbGUuY29thwR/AAABhxAAAAAAAAAAAAAAAAAA
154+
AAABMA0GCSqGSIb3DQEBCwUAA4GBAFpdiiM5YAQQN0H5ZMNuHWGlprjp7qVilO8/
155+
WFePZRWY2vQF8g7/c1cX4bPqG+qFJd+9j2UZNjhadNfMCxvu6BY7NCupOHVHmnRQ
156+
ocvkPoSqobE7qDPfiUuU1J+61Libu6b2IjV3/K9pvZkLiBrqn0YhoXXa0PG+rG1L
157+
9X7+mb5z
158+
-----END CERTIFICATE-----`)
159+
expiredKey := heredoc.Doc(`
160+
-----BEGIN RSA PRIVATE KEY-----
161+
MIICXQIBAAKBgQDjTYA1wZNkNYv2iY08LdNyqUN50G5TEtyF/x/SVsOrWulLm5Sg
162+
Tmg/9qOV7RTPkO1wxJ82MPPAiuSh3cno5CaOSAaVI0GCWRc1CdQQvC8O8sunV1mA
163+
SW6EUdUvtSYZDCTraT3nL/uHE2sp6ZWI9FjAY3ELYYNkTO7XaclGKbKdqwIDAQAB
164+
AoGBAJPFWKqZ9CZboWhfuE/9Qs/yNonE9VRQmMkMOTXXblHCQpUCyjcFgkTDJUpc
165+
3QCsKZD8Yr0qSe1M3qJUu+UKHf18LqwiL/ynnalYggxIFS5/SidWCngKvIuEfkLK
166+
VsnCK3jt5qx21iljGHU6bQZHnHB9IGEiBYcnQlvvw/WdvRDBAkEA8/KMpJVwnI1W
167+
7fzcZ1+mbMeSJoAVIa9u7MgI+LIRZMokDRYeAMvEjm3GYpZDqA5l1dp7KochMep/
168+
0vSSTHt7ewJBAO6IbcUIDhXuh2qdxR/Xk5DdDCoxaD1o4ivyj9JsSlGa9JWD7kKN
169+
6ZFFrn8i7uQuniC1Rwc/4yHhs6OqbiF695ECQQCBwVKzvFUwwDEr1yK4zXStSZ3g
170+
YqJaz4CV63RyK+z6ilaQq2H8FGaRR6yNBdYozre1/0ciAMxUS6H/6Fzk141/AkBe
171+
SguqIP8AaGObH3Z2mc65KsfOPe2IqNcOrDlx4mCWVXxtRdN+933mcPcDRpnMFSlo
172+
oH/NO9Ha6M8L2SjjjyohAkBJHU61+OWz/TAy1nxsMbFsISLn/JrdEZIf2uFORlDN
173+
Z3/XIQ+yeg4Jk1VbTMZ0/fHf9xMFR8acC/7n7jxnzQau
174+
-----END RSA PRIVATE KEY-----`)
175+
176+
server, err := newTLSServer(expiredCert, expiredKey)
177+
if err != nil {
178+
t.Errorf(err.Error())
179+
}
180+
server.StartTLS()
181+
defer server.Close()
182+
183+
testCases := map[string]struct {
184+
serverURL string
185+
skipTLSVerify bool
186+
expectedErrMsg *regexp.Regexp
187+
}{
188+
"succeed skipping tls": {
189+
serverURL: server.URL,
190+
skipTLSVerify: true,
191+
},
192+
"certificate expired": {
193+
serverURL: server.URL,
194+
expectedErrMsg: regexp.MustCompile(`The server is using an invalid certificate(.*)has expired`),
195+
},
196+
}
197+
198+
for name, test := range testCases {
199+
t.Logf("evaluating test: %s", name)
200+
options := &LoginOptions{
201+
Server: test.serverURL,
202+
InsecureTLS: test.skipTLSVerify,
203+
StartingKubeConfig: &kclientcmdapi.Config{},
204+
}
205+
206+
if _, err = options.getClientConfig(); err != nil {
207+
if !test.expectedErrMsg.MatchString(err.Error()) {
208+
t.Errorf("%s: expected error %q but got %q", name, test.expectedErrMsg, err)
209+
}
210+
if test.expectedErrMsg == nil {
211+
t.Errorf("%s: unexpected error: %v", name, err)
212+
}
213+
} else {
214+
if test.expectedErrMsg != nil {
215+
t.Errorf("%s: expected error but got nothing", name)
216+
}
217+
}
218+
}
219+
}
220+
58221
func TestDialToHTTPServer(t *testing.T) {
59222
invoked := make(chan struct{}, 1)
60223
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -92,3 +255,60 @@ func TestDialToHTTPServer(t *testing.T) {
92255
}
93256
}
94257
}
258+
259+
func TestDialToHTTPSServer(t *testing.T) {
260+
invoked := make(chan struct{}, 1)
261+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
262+
invoked <- struct{}{}
263+
w.WriteHeader(http.StatusOK)
264+
}))
265+
defer server.Close()
266+
267+
testCases := map[string]struct {
268+
serverURL string
269+
skipTLSVerify bool
270+
evalExpectedErr func(error) bool
271+
}{
272+
"succeed dialing": {
273+
serverURL: server.URL,
274+
skipTLSVerify: true,
275+
},
276+
"certificate unknown": {
277+
serverURL: server.URL,
278+
evalExpectedErr: clientcmd.IsCertificateAuthorityUnknown,
279+
},
280+
}
281+
282+
for name, test := range testCases {
283+
t.Logf("evaluating test: %s", name)
284+
clientConfig := &restclient.Config{
285+
Host: test.serverURL,
286+
Insecure: test.skipTLSVerify,
287+
}
288+
if err := dialToServer(*clientConfig); err != nil {
289+
if test.evalExpectedErr == nil || !test.evalExpectedErr(err) {
290+
t.Errorf("%s: unexpected error: %v", name, err)
291+
}
292+
} else {
293+
if test.evalExpectedErr != nil {
294+
t.Errorf("%s: expected error but got nothing", name)
295+
}
296+
}
297+
}
298+
}
299+
300+
func newTLSServer(certString, keyString string) (*httptest.Server, error) {
301+
invoked := make(chan struct{}, 1)
302+
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
303+
invoked <- struct{}{}
304+
w.WriteHeader(http.StatusOK)
305+
}))
306+
cert, err := tls.X509KeyPair([]byte(certString), []byte(keyString))
307+
if err != nil {
308+
return nil, fmt.Errorf("error configuring server cert: %s", err)
309+
}
310+
server.TLS = &tls.Config{
311+
Certificates: []tls.Certificate{cert},
312+
}
313+
return server, nil
314+
}

pkg/cmd/util/clientcmd/errors.go

+28
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package clientcmd
22

33
import (
4+
"crypto/x509"
45
"errors"
56
"fmt"
67
"strings"
@@ -13,6 +14,8 @@ const (
1314
unknownReason = iota
1415
noServerFoundReason
1516
certificateAuthorityUnknownReason
17+
certificateHostnameErrorReason
18+
certificateInvalidReason
1619
configurationInvalidReason
1720
tlsOversizedRecordReason
1821

@@ -50,6 +53,12 @@ func GetPrettyMessageForServer(err error, serverName string) string {
5053
serverName = "server"
5154
}
5255
return fmt.Sprintf(tlsOversizedRecordMsg, err, serverName)
56+
57+
case certificateHostnameErrorReason:
58+
return fmt.Sprintf("The server is using a certificate that does not match its hostname: %s", err)
59+
60+
case certificateInvalidReason:
61+
return fmt.Sprintf("The server is using an invalid certificate: %s", err)
5362
}
5463

5564
return err.Error()
@@ -91,6 +100,17 @@ func IsTLSOversizedRecord(err error) bool {
91100
return detectReason(err) == tlsOversizedRecordReason
92101
}
93102

103+
// IsCertificateHostnameError checks whether the set of authorized names doesn't match the requested name
104+
func IsCertificateHostnameError(err error) bool {
105+
return detectReason(err) == certificateHostnameErrorReason
106+
}
107+
108+
// IsCertificateInvalid checks whether the certificate is invalid for reasons like expired, CA not authorized
109+
// to sign, there are too many cert intermediates, or the cert usage is not valid for the wanted purpose.
110+
func IsCertificateInvalid(err error) bool {
111+
return detectReason(err) == certificateInvalidReason
112+
}
113+
94114
func detectReason(err error) int {
95115
if err != nil {
96116
switch {
@@ -103,6 +123,14 @@ func detectReason(err error) int {
103123
case strings.Contains(err.Error(), "tls: oversized record received"):
104124
return tlsOversizedRecordReason
105125
}
126+
switch err.(type) {
127+
case x509.UnknownAuthorityError:
128+
return certificateAuthorityUnknownReason
129+
case x509.HostnameError:
130+
return certificateHostnameErrorReason
131+
case x509.CertificateInvalidError:
132+
return certificateInvalidReason
133+
}
106134
}
107135
return unknownReason
108136
}

0 commit comments

Comments
 (0)