Skip to content

Commit 93729f9

Browse files
Merge pull request #30660 from openshift-cherrypick-robot/cherry-pick-30536-to-release-4.21
[release-4.21] OCPBUGS-71229:Unrevert tls tests with fixes
2 parents b4c4ed0 + 49b8b4b commit 93729f9

File tree

1 file changed

+224
-34
lines changed

1 file changed

+224
-34
lines changed

test/extended/apiserver/tls.go

Lines changed: 224 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,254 @@
11
package apiserver
22

33
import (
4+
"context"
45
"crypto/tls"
6+
"fmt"
7+
"io"
8+
"math/rand"
9+
"os/exec"
10+
"strings"
11+
"time"
512

613
g "github.com/onsi/ginkgo/v2"
14+
o "github.com/onsi/gomega"
15+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
16+
e2e "k8s.io/kubernetes/test/e2e/framework"
717

18+
configv1 "github.com/openshift/api/config/v1"
819
"github.com/openshift/library-go/pkg/crypto"
920
exutil "github.com/openshift/origin/test/extended/util"
1021
)
1122

23+
const (
24+
namespace = "apiserver-tls-test"
25+
)
26+
27+
// This test only checks whether components are serving the proper TLS version based
28+
// on the expected version set in the TLS profile config. It is a part of the
29+
// openshift/conformance/parallel test suite, and it is expected that there are jobs
30+
// which run that entire conformance suite against clusters running any TLS profiles
31+
// that there is a desire to test.
1232
var _ = g.Describe("[sig-api-machinery][Feature:APIServer]", func() {
1333
defer g.GinkgoRecover()
1434

15-
oc := exutil.NewCLI("apiserver")
35+
var oc = exutil.NewCLI(namespace)
36+
var ctx = context.Background()
1637

17-
g.It("TestTLSDefaults", func() {
18-
g.Skip("skipping because it was broken in master")
38+
g.BeforeEach(func() {
39+
isMicroShift, err := exutil.IsMicroShiftCluster(oc.AdminKubeClient())
40+
o.Expect(err).NotTo(o.HaveOccurred())
1941

20-
t := g.GinkgoT()
21-
// Verify we fail with TLS versions less than the default, and work with TLS versions >= the default
22-
for _, tlsVersionName := range crypto.ValidTLSVersions() {
23-
tlsVersion := crypto.TLSVersionOrDie(tlsVersionName)
24-
expectSuccess := tlsVersion >= crypto.DefaultTLSVersion()
25-
config := &tls.Config{MinVersion: tlsVersion, MaxVersion: tlsVersion, InsecureSkipVerify: true}
26-
27-
{
28-
conn, err := tls.Dial("tcp4", oc.AdminConfig().Host, config)
29-
if err == nil {
30-
conn.Close()
42+
isHyperShift, err := exutil.IsHypershift(ctx, oc.AdminConfigClient())
43+
o.Expect(err).NotTo(o.HaveOccurred())
44+
45+
if isMicroShift || isHyperShift {
46+
g.Skip("TLS configuration for the apiserver resource is not applicable to MicroShift or HyperShift clusters - skipping")
47+
}
48+
})
49+
50+
g.It("TestTLSMinimumVersions", func() {
51+
52+
g.By("Getting the APIServer configuration")
53+
config, err := oc.AdminConfigClient().ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{})
54+
o.Expect(err).NotTo(o.HaveOccurred())
55+
56+
g.By("Determining expected TLS behavior based on the cluster's TLS profile")
57+
var tlsShouldWork, tlsShouldNotWork *tls.Config
58+
switch {
59+
case config.Spec.TLSSecurityProfile == nil,
60+
config.Spec.TLSSecurityProfile.Type == configv1.TLSProfileIntermediateType:
61+
tlsShouldWork = &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true}
62+
tlsShouldNotWork = &tls.Config{MinVersion: tls.VersionTLS11, MaxVersion: tls.VersionTLS11, InsecureSkipVerify: true}
63+
g.By("Using intermediate TLS profile: connections with TLS ≥1.2 should work, <1.2 should fail")
64+
case config.Spec.TLSSecurityProfile.Type == configv1.TLSProfileModernType:
65+
tlsShouldWork = &tls.Config{MinVersion: tls.VersionTLS13, MaxVersion: tls.VersionTLS13, InsecureSkipVerify: true}
66+
tlsShouldNotWork = &tls.Config{MinVersion: tls.VersionTLS12, MaxVersion: tls.VersionTLS12, InsecureSkipVerify: true}
67+
g.By("Using modern TLS profile: only TLS 1.3 connections should succeed")
68+
default:
69+
g.Skip("Only intermediate or modern profiles are tested")
70+
}
71+
72+
targets := []struct {
73+
name, namespace, port string
74+
}{
75+
{"apiserver", "openshift-kube-apiserver", "443"},
76+
{"oauth-openshift", "openshift-authentication", "443"},
77+
{"kube-controller-manager", "openshift-kube-controller-manager", "443"},
78+
{"scheduler", "openshift-kube-scheduler", "443"},
79+
{"api", "openshift-apiserver", "443"},
80+
{"api", "openshift-oauth-apiserver", "443"},
81+
{"machine-config-controller", "openshift-machine-config-operator", "9001"},
82+
}
83+
84+
g.By("Verifying TLS behavior for core control plane components")
85+
for _, target := range targets {
86+
g.By(fmt.Sprintf("Checking %s/%s on port %s", target.namespace, target.name, target.port))
87+
err = forwardPortAndExecute(target.name, target.namespace, target.port,
88+
func(port int) error { return checkTLSConnection(port, tlsShouldWork, tlsShouldNotWork) })
89+
o.Expect(err).NotTo(o.HaveOccurred())
90+
}
91+
92+
g.By("Checking etcd's TLS behavior")
93+
err = forwardPortAndExecute("etcd", "openshift-etcd", "2379", func(port int) error {
94+
conn, err := tls.Dial("tcp", fmt.Sprintf("localhost:%d", port), tlsShouldWork)
95+
if err != nil {
96+
if !strings.Contains(err.Error(), "remote error: tls: bad certificate") {
97+
return fmt.Errorf("should work: %w", err)
3198
}
32-
if success := err == nil; success != expectSuccess {
33-
t.Errorf("Expected success %v, got %v with TLS version %s dialing master", expectSuccess, success, tlsVersionName)
99+
} else {
100+
err = conn.Close()
101+
if err != nil {
102+
return fmt.Errorf("failed to close connection: %w", err)
34103
}
35104
}
36-
}
105+
conn, err = tls.Dial("tcp", fmt.Sprintf("localhost:%d", port), tlsShouldNotWork)
106+
if err == nil {
107+
return fmt.Errorf("should not work: connection unexpectedly succeeded, closing conn status: %v", conn.Close())
108+
}
109+
return nil
110+
})
111+
o.Expect(err).NotTo(o.HaveOccurred())
112+
})
113+
114+
g.It("TestTLSDefaults", func() {
115+
t := g.GinkgoT()
116+
117+
_, err := e2e.LoadClientset(true)
118+
o.Expect(err).NotTo(o.HaveOccurred())
37119

38-
// Verify the only ciphers we work with are in the default set.
39-
// Not all default ciphers will succeed because they depend on the serving cert type.
40-
defaultCiphers := map[uint16]bool{}
41-
for _, defaultCipher := range crypto.DefaultCiphers() {
42-
defaultCiphers[defaultCipher] = true
120+
g.By("Getting the APIServer config")
121+
config, err := oc.AdminConfigClient().ConfigV1().APIServers().Get(ctx, "cluster", metav1.GetOptions{})
122+
o.Expect(err).NotTo(o.HaveOccurred())
123+
124+
if config.Spec.TLSSecurityProfile != nil &&
125+
config.Spec.TLSSecurityProfile.Type != configv1.TLSProfileIntermediateType {
126+
g.Skip("Cluster TLS profile is not default (intermediate), skipping cipher defaults check")
43127
}
44-
for _, cipherName := range crypto.ValidCipherSuites() {
45-
cipher, err := crypto.CipherSuite(cipherName)
46-
if err != nil {
47-
t.Fatal(err)
128+
129+
g.By("Verifying TLS version and cipher behavior via port-forward to apiserver")
130+
err = forwardPortAndExecute("apiserver", "openshift-kube-apiserver", "443", func(port int) error {
131+
host := fmt.Sprintf("localhost:%d", port)
132+
t.Logf("Testing TLS versions and ciphers against %s", host)
133+
134+
// Test TLS versions
135+
for _, tlsVersionName := range crypto.ValidTLSVersions() {
136+
tlsVersion := crypto.TLSVersionOrDie(tlsVersionName)
137+
expectSuccess := tlsVersion >= crypto.DefaultTLSVersion()
138+
cfg := &tls.Config{MinVersion: tlsVersion, MaxVersion: tlsVersion, InsecureSkipVerify: true}
139+
140+
t.Logf("Testing TLS version %s (0x%04x), expectSuccess=%v", tlsVersionName, tlsVersion, expectSuccess)
141+
conn, dialErr := tls.Dial("tcp", host, cfg)
142+
if dialErr == nil {
143+
t.Logf("TLS %s succeeded, negotiated version: 0x%04x", tlsVersionName, conn.ConnectionState().Version)
144+
closeErr := conn.Close()
145+
if closeErr != nil {
146+
return fmt.Errorf("failed to close connection: %v", closeErr)
147+
}
148+
} else {
149+
t.Logf("TLS %s failed with error: %v", tlsVersionName, dialErr)
150+
}
151+
if success := dialErr == nil; success != expectSuccess {
152+
return fmt.Errorf("expected success %v, got %v with TLS version %s", expectSuccess, success, tlsVersionName)
153+
}
154+
}
155+
156+
// Test cipher suites
157+
defaultCiphers := map[uint16]bool{}
158+
for _, c := range crypto.DefaultCiphers() {
159+
defaultCiphers[c] = true
48160
}
49-
expectFailure := !defaultCiphers[cipher]
50-
config := &tls.Config{CipherSuites: []uint16{cipher}, InsecureSkipVerify: true}
51161

52-
{
53-
conn, err := tls.Dial("tcp4", oc.AdminConfig().Host, config)
54-
if err == nil {
55-
conn.Close()
162+
for _, cipherName := range crypto.ValidCipherSuites() {
163+
cipher, err := crypto.CipherSuite(cipherName)
164+
if err != nil {
165+
return err
166+
}
167+
expectFailure := !defaultCiphers[cipher]
168+
// Constrain to TLS 1.2 because the intermediate profile allows both TLS 1.2 and TLS 1.3.
169+
// If MaxVersion is unspecified, the client negotiates TLS 1.3 when the server supports it.
170+
// TLS 1.3 does not support configuring cipher suites (predetermined by the spec), so
171+
// specifying any cipher suite (RC4 or otherwise) has no effect with TLS 1.3.
172+
// By forcing TLS 1.2, we can actually test the cipher suite restrictions.
173+
cfg := &tls.Config{
174+
CipherSuites: []uint16{cipher},
175+
MinVersion: tls.VersionTLS12,
176+
MaxVersion: tls.VersionTLS12,
177+
InsecureSkipVerify: true,
178+
}
179+
180+
conn, dialErr := tls.Dial("tcp", host, cfg)
181+
if dialErr == nil {
182+
closeErr := conn.Close()
56183
if expectFailure {
57-
t.Errorf("Expected failure on cipher %s, got success dialing master", cipherName)
184+
return fmt.Errorf("expected failure on cipher %s, got success. Closing conn: %v", cipherName, closeErr)
58185
}
59186
}
60187
}
61-
}
62188

189+
return nil
190+
})
191+
o.Expect(err).NotTo(o.HaveOccurred())
63192
})
64193
})
194+
195+
func forwardPortAndExecute(serviceName, namespace, remotePort string, toExecute func(localPort int) error) error {
196+
var err error
197+
for i := 0; i < 3; i++ {
198+
if err = func() error {
199+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
200+
defer cancel()
201+
localPort := rand.Intn(65534-1025) + 1025
202+
args := []string{"port-forward", fmt.Sprintf("svc/%s", serviceName), fmt.Sprintf("%d:%s", localPort, remotePort), "-n", namespace}
203+
204+
cmd := exec.CommandContext(ctx, "oc", args...)
205+
stdout, stderr, err := e2e.StartCmdAndStreamOutput(cmd)
206+
if err != nil {
207+
return err
208+
}
209+
defer stdout.Close()
210+
defer stderr.Close()
211+
defer e2e.TryKill(cmd)
212+
213+
e2e.Logf("oc port-forward output: %s", readPartialFrom(stdout, 1024))
214+
return toExecute(localPort)
215+
}(); err == nil {
216+
return nil
217+
} else {
218+
e2e.Logf("failed to start oc port-forward command or test: %v", err)
219+
time.Sleep(2 * time.Second)
220+
}
221+
}
222+
return err
223+
}
224+
225+
func readPartialFrom(r io.Reader, maxBytes int) string {
226+
buf := make([]byte, maxBytes)
227+
n, err := r.Read(buf)
228+
if err != nil && err != io.EOF {
229+
return fmt.Sprintf("error reading: %v", err)
230+
}
231+
return string(buf[:n])
232+
}
233+
234+
func checkTLSConnection(port int, tlsShouldWork, tlsShouldNotWork *tls.Config) error {
235+
conn, err := tls.Dial("tcp", fmt.Sprintf("localhost:%d", port), tlsShouldWork)
236+
if err != nil {
237+
return fmt.Errorf("should work: %w", err)
238+
}
239+
err = conn.Close()
240+
if err != nil {
241+
return fmt.Errorf("failed to close connection: %w", err)
242+
}
243+
244+
conn, err = tls.Dial("tcp", fmt.Sprintf("localhost:%d", port), tlsShouldNotWork)
245+
if err == nil {
246+
return fmt.Errorf("should not work: connection unexpectedly succeeded, closing conn status: %v", conn.Close())
247+
}
248+
if !strings.Contains(err.Error(), "protocol version") &&
249+
!strings.Contains(err.Error(), "no supported versions satisfy") &&
250+
!strings.Contains(err.Error(), "handshake failure") {
251+
return fmt.Errorf("should not work: got error, but not a TLS version mismatch: %w", err)
252+
}
253+
return nil
254+
}

0 commit comments

Comments
 (0)