Skip to content

Commit c8ec4eb

Browse files
committed
Fix PreStop hook to use HTTPS with system-internal-tls
The PreStop hook was always using HTTP to contact queue-proxy, causing TLS errors when system-internal-tls was enabled. Now the scheme is dynamically set based on the TLS configuration.
1 parent 0ee5fc0 commit c8ec4eb

File tree

2 files changed

+136
-18
lines changed

2 files changed

+136
-18
lines changed

pkg/reconciler/revision/resources/deploy.go

Lines changed: 26 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -97,20 +97,29 @@ var (
9797
MountPath: queue.PodInfoDirectory,
9898
ReadOnly: true,
9999
}
100+
)
101+
102+
// makeUserLifecycle creates the lifecycle configuration for user containers.
103+
// This PreStop hook is actually calling an endpoint on the queue-proxy
104+
// because of the way PreStop hooks are called by kubelet. We use this
105+
// to block the user-container from exiting before the queue-proxy is ready
106+
// to exit so we can guarantee that there are no more requests in flight.
107+
func makeUserLifecycle(systemInternalTLSEnabled bool) *corev1.Lifecycle {
108+
scheme := corev1.URISchemeHTTP
109+
if systemInternalTLSEnabled {
110+
scheme = corev1.URISchemeHTTPS
111+
}
100112

101-
// This PreStop hook is actually calling an endpoint on the queue-proxy
102-
// because of the way PreStop hooks are called by kubelet. We use this
103-
// to block the user-container from exiting before the queue-proxy is ready
104-
// to exit so we can guarantee that there are no more requests in flight.
105-
userLifecycle = &corev1.Lifecycle{
113+
return &corev1.Lifecycle{
106114
PreStop: &corev1.LifecycleHandler{
107115
HTTPGet: &corev1.HTTPGetAction{
108-
Port: intstr.FromInt(networking.QueueAdminPort),
109-
Path: queue.RequestQueueDrainPath,
116+
Port: intstr.FromInt(networking.QueueAdminPort),
117+
Path: queue.RequestQueueDrainPath,
118+
Scheme: scheme,
110119
},
111120
},
112121
}
113-
)
122+
}
114123

115124
func addToken(tokenVolume *corev1.Volume, filename string, audience string, expiry *int64) {
116125
if filename == "" || audience == "" {
@@ -205,7 +214,7 @@ func makePodSpec(rev *v1.Revision, cfg *config.Config) (*corev1.PodSpec, error)
205214
extraVolumes = append(extraVolumes, certVolume(networking.ServingCertName))
206215
}
207216

208-
podSpec := BuildPodSpec(rev, append(BuildUserContainers(rev), *queueContainer), cfg)
217+
podSpec := BuildPodSpec(rev, append(BuildUserContainers(rev, cfg), *queueContainer), cfg)
209218
podSpec.Volumes = append(podSpec.Volumes, extraVolumes...)
210219

211220
if val := cfg.Deployment.PodRuntimeClassName(rev.ObjectMeta.Labels); podSpec.RuntimeClassName == nil {
@@ -236,14 +245,14 @@ func makePodSpec(rev *v1.Revision, cfg *config.Config) (*corev1.PodSpec, error)
236245
}
237246

238247
// BuildUserContainers makes an array of containers from the Revision template.
239-
func BuildUserContainers(rev *v1.Revision) []corev1.Container {
248+
func BuildUserContainers(rev *v1.Revision, cfg *config.Config) []corev1.Container {
240249
containers := make([]corev1.Container, 0, len(rev.Spec.PodSpec.Containers))
241250
for i := range rev.Spec.PodSpec.Containers {
242251
var container corev1.Container
243252
if len(rev.Spec.PodSpec.Containers[i].Ports) != 0 || len(rev.Spec.PodSpec.Containers) == 1 {
244-
container = makeServingContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev)
253+
container = makeServingContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev, cfg)
245254
} else {
246-
container = makeContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev)
255+
container = makeContainer(*rev.Spec.PodSpec.Containers[i].DeepCopy(), rev, cfg)
247256
}
248257
// The below logic is safe because the image digests in Status.ContainerStatus will have been resolved
249258
// before this method is called. We check for an empty array here because the method can also be
@@ -258,10 +267,11 @@ func BuildUserContainers(rev *v1.Revision) []corev1.Container {
258267
return containers
259268
}
260269

261-
func makeContainer(container corev1.Container, rev *v1.Revision) corev1.Container {
270+
func makeContainer(container corev1.Container, rev *v1.Revision, cfg *config.Config) corev1.Container {
262271
// Adding or removing an overwritten corev1.Container field here? Don't forget to
263272
// update the fieldmasks / validations in pkg/apis/serving
264-
container.Lifecycle = userLifecycle
273+
systemInternalTLSEnabled := cfg != nil && cfg.Network.SystemInternalTLSEnabled()
274+
container.Lifecycle = makeUserLifecycle(systemInternalTLSEnabled)
265275
container.Env = append(container.Env, getKnativeEnvVar(rev)...)
266276

267277
// Explicitly disable stdin and tty allocation
@@ -282,13 +292,13 @@ func makeContainer(container corev1.Container, rev *v1.Revision) corev1.Containe
282292
return container
283293
}
284294

285-
func makeServingContainer(servingContainer corev1.Container, rev *v1.Revision) corev1.Container {
295+
func makeServingContainer(servingContainer corev1.Container, rev *v1.Revision, cfg *config.Config) corev1.Container {
286296
userPort := getUserPort(rev)
287297
userPortStr := strconv.Itoa(int(userPort))
288298
// Replacement is safe as only up to a single port is allowed on the Revision
289299
servingContainer.Ports = buildContainerPorts(userPort)
290300
servingContainer.Env = append(servingContainer.Env, buildUserPortEnv(userPortStr))
291-
container := makeContainer(servingContainer, rev)
301+
container := makeContainer(servingContainer, rev, cfg)
292302
// If the user provides a liveness probe, we should rewrite in the port on the user-container for them.
293303
rewriteUserLivenessProbe(container.LivenessProbe, int(userPort))
294304
return container

pkg/reconciler/revision/resources/deploy_test.go

Lines changed: 110 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"k8s.io/apimachinery/pkg/util/intstr"
3232
"k8s.io/apimachinery/pkg/util/sets"
3333

34+
netcfg "knative.dev/networking/pkg/config"
3435
netheader "knative.dev/networking/pkg/http/header"
3536
"knative.dev/pkg/kmeta"
3637
"knative.dev/pkg/ptr"
@@ -41,6 +42,7 @@ import (
4142
v1 "knative.dev/serving/pkg/apis/serving/v1"
4243
"knative.dev/serving/pkg/autoscaler/config/autoscalerconfig"
4344
"knative.dev/serving/pkg/deployment"
45+
"knative.dev/serving/pkg/networking"
4446
"knative.dev/serving/pkg/observability"
4547
"knative.dev/serving/pkg/queue"
4648

@@ -56,7 +58,7 @@ var (
5658
Name: servingContainerName,
5759
Image: "busybox",
5860
Ports: buildContainerPorts(v1.DefaultUserPort),
59-
Lifecycle: userLifecycle,
61+
Lifecycle: makeUserLifecycle(false),
6062
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
6163
Stdin: false,
6264
TTY: false,
@@ -253,7 +255,7 @@ func defaultSidecarContainer(containerName string) *corev1.Container {
253255
return &corev1.Container{
254256
Name: containerName,
255257
Image: "ubuntu",
256-
Lifecycle: userLifecycle,
258+
Lifecycle: makeUserLifecycle(false),
257259
TerminationMessagePolicy: corev1.TerminationMessageFallbackToLogsOnError,
258260
Stdin: false,
259261
TTY: false,
@@ -1749,6 +1751,112 @@ func TestMakePodSpec(t *testing.T) {
17491751
}
17501752
}
17511753

1754+
func TestMakePodSpecWithSystemInternalTLS(t *testing.T) {
1755+
tests := []struct {
1756+
name string
1757+
rev *v1.Revision
1758+
tlsEnabled bool
1759+
wantScheme corev1.URIScheme
1760+
}{{
1761+
name: "system internal TLS disabled",
1762+
rev: revision("bar", "foo",
1763+
withContainers([]corev1.Container{{
1764+
Name: servingContainerName,
1765+
Image: "busybox",
1766+
ReadinessProbe: withTCPReadinessProbe(v1.DefaultUserPort),
1767+
}}),
1768+
WithContainerStatuses([]v1.ContainerStatus{{
1769+
ImageDigest: "busybox@sha256:deadbeef",
1770+
}}),
1771+
),
1772+
tlsEnabled: false,
1773+
wantScheme: corev1.URISchemeHTTP,
1774+
}, {
1775+
name: "system internal TLS enabled",
1776+
rev: revision("bar", "foo",
1777+
withContainers([]corev1.Container{{
1778+
Name: servingContainerName,
1779+
Image: "busybox",
1780+
ReadinessProbe: withTCPReadinessProbe(v1.DefaultUserPort),
1781+
}}),
1782+
WithContainerStatuses([]v1.ContainerStatus{{
1783+
ImageDigest: "busybox@sha256:deadbeef",
1784+
}}),
1785+
),
1786+
tlsEnabled: true,
1787+
wantScheme: corev1.URISchemeHTTPS,
1788+
}}
1789+
1790+
for _, test := range tests {
1791+
t.Run(test.name, func(t *testing.T) {
1792+
cfg := revConfig()
1793+
if test.tlsEnabled {
1794+
cfg.Network.SystemInternalTLS = netcfg.EncryptionEnabled
1795+
}
1796+
got, err := makePodSpec(test.rev, cfg)
1797+
if err != nil {
1798+
t.Fatal("makePodSpec returned error:", err)
1799+
}
1800+
// Check that all user containers have the correct lifecycle scheme
1801+
for _, container := range got.Containers {
1802+
if container.Name == QueueContainerName {
1803+
continue
1804+
}
1805+
if container.Lifecycle == nil || container.Lifecycle.PreStop == nil ||
1806+
container.Lifecycle.PreStop.HTTPGet == nil {
1807+
t.Errorf("Container %s missing PreStop HTTPGet", container.Name)
1808+
continue
1809+
}
1810+
if container.Lifecycle.PreStop.HTTPGet.Scheme != test.wantScheme {
1811+
t.Errorf("Container %s PreStop scheme = %v, want %v",
1812+
container.Name, container.Lifecycle.PreStop.HTTPGet.Scheme, test.wantScheme)
1813+
}
1814+
}
1815+
})
1816+
}
1817+
}
1818+
1819+
func TestMakeUserLifecycle(t *testing.T) {
1820+
tests := []struct {
1821+
name string
1822+
systemInternalTLSEnabled bool
1823+
wantScheme corev1.URIScheme
1824+
}{{
1825+
name: "system internal TLS disabled",
1826+
systemInternalTLSEnabled: false,
1827+
wantScheme: corev1.URISchemeHTTP,
1828+
}, {
1829+
name: "system internal TLS enabled",
1830+
systemInternalTLSEnabled: true,
1831+
wantScheme: corev1.URISchemeHTTPS,
1832+
}}
1833+
1834+
for _, test := range tests {
1835+
t.Run(test.name, func(t *testing.T) {
1836+
got := makeUserLifecycle(test.systemInternalTLSEnabled)
1837+
if got == nil {
1838+
t.Fatal("makeUserLifecycle returned nil")
1839+
}
1840+
if got.PreStop == nil {
1841+
t.Fatal("makeUserLifecycle returned nil PreStop")
1842+
}
1843+
if got.PreStop.HTTPGet == nil {
1844+
t.Fatal("makeUserLifecycle returned nil HTTPGet")
1845+
}
1846+
if got.PreStop.HTTPGet.Scheme != test.wantScheme {
1847+
t.Errorf("makeUserLifecycle scheme = %v, want %v", got.PreStop.HTTPGet.Scheme, test.wantScheme)
1848+
}
1849+
// Verify other PreStop hook properties remain the same
1850+
if got.PreStop.HTTPGet.Port.IntValue() != networking.QueueAdminPort {
1851+
t.Errorf("makeUserLifecycle port = %v, want %v", got.PreStop.HTTPGet.Port.IntValue(), networking.QueueAdminPort)
1852+
}
1853+
if got.PreStop.HTTPGet.Path != queue.RequestQueueDrainPath {
1854+
t.Errorf("makeUserLifecycle path = %v, want %v", got.PreStop.HTTPGet.Path, queue.RequestQueueDrainPath)
1855+
}
1856+
})
1857+
}
1858+
}
1859+
17521860
var quantityComparer = cmp.Comparer(func(x, y resource.Quantity) bool {
17531861
return x.Cmp(y) == 0
17541862
})

0 commit comments

Comments
 (0)