Skip to content

Commit 298b544

Browse files
committed
oc login: Respect insecure flag from kubeconfig
When running oc login, the insecure flag from kubeconfig is not consulted properly when calling getClientConfig(). This is now fixed. Assisted-by: Claude Code
1 parent 345800d commit 298b544

File tree

3 files changed

+127
-17
lines changed

3 files changed

+127
-17
lines changed

pkg/cli/login/helpers.go

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,20 +30,21 @@ func getMatchingClusters(clientConfig restclient.Config, kubeconfig clientcmdapi
3030
return ret
3131
}
3232

33-
// findExistingClientCA returns *either* the existing client CA file name as a string,
34-
// *or* data in a []byte for a given host, and true if it exists in the given config
35-
func findExistingClientCA(host string, kubeconfig clientcmdapi.Config) (string, []byte, bool) {
33+
// findCluster returns the cluster matching the given host and optionally the cond function, which can be nil.
34+
func findCluster(host string, kubeconfig clientcmdapi.Config, cond func(*clientcmdapi.Cluster) bool) *clientcmdapi.Cluster {
3635
for _, cluster := range kubeconfig.Clusters {
37-
if cluster.Server == host {
38-
if len(cluster.CertificateAuthority) > 0 {
39-
return cluster.CertificateAuthority, nil, true
40-
}
41-
if len(cluster.CertificateAuthorityData) > 0 {
42-
return "", cluster.CertificateAuthorityData, true
43-
}
36+
if cluster.Server == host && (cond == nil || cond(cluster)) {
37+
return cluster
4438
}
4539
}
46-
return "", nil, false
40+
return nil
41+
}
42+
43+
// findClusterWithCA returns the cluster matching the given host and having CertificateAuthority or CertificateAuthorityData set.
44+
func findClusterWithCA(host string, kubeconfig clientcmdapi.Config) *clientcmdapi.Cluster {
45+
return findCluster(host, kubeconfig, func(c *clientcmdapi.Cluster) bool {
46+
return len(c.CertificateAuthorityData) > 0 || len(c.CertificateAuthority) > 0
47+
})
4748
}
4849

4950
// dialToServer takes the Server URL from the given clientConfig and dials to

pkg/cli/login/loginoptions.go

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,16 +162,26 @@ func (o *LoginOptions) getClientConfig() (*restclient.Config, error) {
162162
clientConfig.Host = o.Server
163163
clientConfig.Insecure = o.InsecureTLS
164164

165-
if !o.InsecureTLS {
166-
// use specified CA or find existing CA
165+
if !clientConfig.Insecure {
166+
// Try to use the specified CA. Then fall back into searching kubeconfig.
167+
// In case no CA is found, still check the kubeconfig for insecure-skip-tls-verify.
167168
if len(o.CAFile) > 0 {
168169
clientConfig.CAFile = o.CAFile
169170
clientConfig.CAData = nil
170-
} else if caFile, caData, ok := findExistingClientCA(clientConfig.Host, *o.StartingKubeConfig); ok {
171-
clientConfig.CAFile = caFile
172-
clientConfig.CAData = caData
171+
} else if cluster := findClusterWithCA(clientConfig.Host, *o.StartingKubeConfig); cluster != nil {
172+
clientConfig.Insecure = cluster.InsecureSkipTLSVerify
173+
clientConfig.CAFile = cluster.CertificateAuthority
174+
clientConfig.CAData = cluster.CertificateAuthorityData
175+
} else if cluster := findCluster(clientConfig.Host, *o.StartingKubeConfig, nil); cluster != nil {
176+
clientConfig.Insecure = cluster.InsecureSkipTLSVerify
173177
}
174178
}
179+
// In case the kubeconfig contained insecure-skip-tls-verify, we must unset the CA settings,
180+
// otherwise an error is returned from k8s transport init machinery.
181+
if clientConfig.Insecure {
182+
clientConfig.CAFile = ""
183+
clientConfig.CAData = nil
184+
}
175185

176186
// try to TCP connect to the server to make sure it's reachable, and discover
177187
// about the need of certificates or insecure TLS
@@ -188,7 +198,7 @@ func (o *LoginOptions) getClientConfig() (*restclient.Config, error) {
188198
// connection or if we already have a cluster stanza that tells us to
189199
// connect to this particular server insecurely
190200
case x509.UnknownAuthorityError, x509.HostnameError, x509.CertificateInvalidError:
191-
if o.InsecureTLS ||
201+
if clientConfig.Insecure ||
192202
hasExistingInsecureCluster(*clientConfig, *o.StartingKubeConfig) ||
193203
promptForInsecureTLS(o.In, o.Out, err) {
194204
clientConfig.Insecure = true

pkg/cli/login/loginoptions_test.go

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
11
package login
22

33
import (
4+
"bytes"
45
"crypto/tls"
6+
"encoding/base64"
57
"encoding/json"
68
"encoding/pem"
79
"fmt"
810
"net/http"
911
"net/http/httptest"
1012
"regexp"
1113
"testing"
14+
"time"
1215

1316
"github.com/MakeNowJust/heredoc"
1417
"github.com/google/go-cmp/cmp"
@@ -20,6 +23,7 @@ import (
2023
kclientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2124

2225
userv1 "github.com/openshift/api/user/v1"
26+
"github.com/openshift/library-go/pkg/crypto"
2327
"github.com/openshift/library-go/pkg/oauth/oauthdiscovery"
2428
cliconfig "github.com/openshift/oc/pkg/helpers/kubeconfig"
2529
)
@@ -377,6 +381,87 @@ func TestDialToHTTPSServer(t *testing.T) {
377381
}
378382
}
379383

384+
func TestGetClientConfig_InsecureSkipTLSVerify(t *testing.T) {
385+
// Test that insecure-skip-tls-verify setting from kubeconfig is respected
386+
// when logging in without the --insecure-skip-tls-verify flag.
387+
388+
// Generate a foo CA cert to use in the kubeconfig. This doesn't match the testing TLS server.
389+
cert, _ := generateCA(t, "test", 1*time.Hour)
390+
certBase64 := base64.StdEncoding.EncodeToString(cert)
391+
392+
server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
393+
w.WriteHeader(http.StatusOK)
394+
}))
395+
defer server.Close()
396+
397+
testCases := map[string]struct {
398+
insecureFlag bool
399+
insecureKubeconfig bool
400+
expectedInsecureClientConfig bool
401+
}{
402+
"command flag set": {
403+
insecureFlag: true,
404+
expectedInsecureClientConfig: true,
405+
},
406+
"kubeconfig flag set": {
407+
insecureKubeconfig: true,
408+
expectedInsecureClientConfig: true,
409+
},
410+
"no flag set": {
411+
insecureFlag: false,
412+
insecureKubeconfig: false,
413+
expectedInsecureClientConfig: false,
414+
},
415+
"both command and kubeconfig flag set": {
416+
insecureFlag: true,
417+
insecureKubeconfig: true,
418+
expectedInsecureClientConfig: true,
419+
},
420+
}
421+
422+
for name, test := range testCases {
423+
t.Run(name, func(t *testing.T) {
424+
startingConfig := &kclientcmdapi.Config{
425+
Clusters: map[string]*kclientcmdapi.Cluster{},
426+
}
427+
if test.insecureKubeconfig {
428+
startingConfig.Clusters["test-cluster"] = &kclientcmdapi.Cluster{
429+
Server: server.URL,
430+
// CertificateAuthority is only set so that we can see getClientConfig works fine when it is set.
431+
// It's actually dropped by getClientConfig since CA cert cannot be set together with insecure.
432+
// That's enforces by the k8s client transport init machinery.
433+
CertificateAuthority: certBase64,
434+
InsecureSkipTLSVerify: true,
435+
}
436+
}
437+
438+
options := &LoginOptions{
439+
Server: server.URL,
440+
InsecureTLS: test.insecureFlag,
441+
StartingKubeConfig: startingConfig,
442+
}
443+
444+
clientConfig, err := options.getClientConfig()
445+
if err != nil {
446+
if test.expectedInsecureClientConfig {
447+
t.Fatalf("Expected to succeed with insecure connection, but got error: %v", err)
448+
} else {
449+
// If we expect secure connection and get a TLS error, that's expected
450+
// since we're using a test server with a self-signed cert.
451+
if err.Error() != certificateAuthorityUnknownMsg {
452+
t.Fatalf("Expected to fail with insecure connection, but got another error: %v", err)
453+
}
454+
return
455+
}
456+
}
457+
458+
if clientConfig.Insecure != test.expectedInsecureClientConfig {
459+
t.Errorf("expected Insecure=%v, got %v", test.expectedInsecureClientConfig, clientConfig.Insecure)
460+
}
461+
})
462+
}
463+
}
464+
380465
func TestPreserveExecProviderOnUsernameLogin(t *testing.T) {
381466
// Test that when using -u flag with existing OIDC credentials,
382467
// the ExecProvider configuration is preserved
@@ -584,3 +669,17 @@ func newTLSServer(certString, keyString string) (*httptest.Server, error) {
584669
}
585670
return server, nil
586671
}
672+
673+
func generateCA(t testing.TB, name string, expiration time.Duration) (cert, key []byte) {
674+
ca, err := crypto.MakeSelfSignedCAConfigForDuration(name, expiration)
675+
if err != nil {
676+
t.Fatal(err)
677+
}
678+
679+
var certBytes, keyBytes bytes.Buffer
680+
if err = ca.WriteCertConfig(&certBytes, &keyBytes); err != nil {
681+
t.Fatal(err)
682+
}
683+
684+
return certBytes.Bytes(), keyBytes.Bytes()
685+
}

0 commit comments

Comments
 (0)