Skip to content

Commit 7254abf

Browse files
committed
fix: disable redirects to PXE
R2 uses ecdsa-with-SHA256 as signature algorithm, which is not supported by PXE. We need to either use RSA or drop the redirects. Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
1 parent 251aee0 commit 7254abf

4 files changed

Lines changed: 107 additions & 23 deletions

File tree

internal/frontend/http/image.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import (
2626
func (f *Frontend) handleImage(ctx context.Context, w http.ResponseWriter, r *http.Request, p httprouter.Params) error {
2727
schematicID := p.ByName("schematic")
2828

29+
// If the request is coming from the external PXE URL we disable redirects.
30+
disableRedirect := r.Host == f.options.ExternalPXEURL.Host
31+
2932
schematic, err := f.schematicFactory.Get(ctx, schematicID)
3033
if err != nil {
3134
return err
@@ -62,7 +65,7 @@ func (f *Frontend) handleImage(ctx context.Context, w http.ResponseWriter, r *ht
6265
return err
6366
}
6467

65-
if asset, ok := asset.(cache.RedirectableAsset); ok && r.Method != http.MethodHead {
68+
if asset, ok := asset.(cache.RedirectableAsset); ok && !disableRedirect && r.Method != http.MethodHead {
6669
var url string
6770

6871
url, err = asset.Redirect()

internal/integration/download_test.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,35 @@ func downloadAsset(ctx context.Context, t *testing.T, baseURL string, schematicI
4646
return resp
4747
}
4848

49+
func downloadNoRedirect(ctx context.Context, t *testing.T, url string) *http.Response {
50+
t.Helper()
51+
52+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
53+
require.NoError(t, err, url)
54+
55+
noRedirectClient := &http.Client{
56+
CheckRedirect: func(req *http.Request, via []*http.Request) error {
57+
// If this is called, it means a redirect was attempted
58+
assert.Failf(t,
59+
"unexpected redirect",
60+
"attempted to redirect to %s (via: %v)",
61+
req.URL.String(), via)
62+
63+
// Prevent following the redirect
64+
return http.ErrUseLastResponse
65+
},
66+
}
67+
68+
resp, err := noRedirectClient.Do(req)
69+
require.NoError(t, err, url)
70+
71+
t.Cleanup(func() {
72+
resp.Body.Close()
73+
})
74+
75+
return resp
76+
}
77+
4978
func downloadAssetAssertCached(ctx context.Context, t *testing.T, baseURL, schematicID, talosVersion, path string, expectedSize int64) {
5079
t.Helper()
5180

internal/integration/integration_test.go

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,22 @@ import (
3333
"github.com/siderolabs/image-factory/internal/remotewrap"
3434
)
3535

36-
func setupFactory(t *testing.T, options cmd.Options) (context.Context, string) {
36+
func setupFactory(t *testing.T, options cmd.Options) (context.Context, string, string) {
3737
t.Helper()
3838

3939
ctx, cancel := context.WithCancel(t.Context())
4040

4141
logger := zaptest.NewLogger(t)
4242

43-
options.HTTPListenAddr = findListenAddr(t)
43+
host, port := findListenAddr(t, "127.0.0.1")
44+
45+
defaultAddr := net.JoinHostPort(host, port)
46+
pxeAddr := net.JoinHostPort("localhost", port)
47+
48+
options.HTTPListenAddr = net.JoinHostPort(host, port)
4449
options.ImageRegistry = imageRegistryFlag
45-
options.ExternalURL = "http://" + options.HTTPListenAddr + "/"
50+
options.ExternalURL = "http://" + defaultAddr + "/"
51+
options.ExternalPXEURL = "http://" + pxeAddr + "/"
4652
options.SchematicServiceRepository = schematicFactoryRepositoryFlag
4753
options.InstallerExternalRepository = installerExternalRepository
4854
options.InstallerInternalRepository = installerInternalRepository
@@ -75,7 +81,7 @@ func setupFactory(t *testing.T, options cmd.Options) (context.Context, string) {
7581
return err == nil
7682
}, 10*time.Second, 10*time.Millisecond)
7783

78-
return ctx, options.HTTPListenAddr
84+
return ctx, defaultAddr, pxeAddr
7985
}
8086

8187
func setupCacheSigningKey(t *testing.T, options *cmd.Options) {
@@ -127,8 +133,7 @@ const (
127133
func setupS3(t *testing.T, pool *dockertest.Pool, bucket string) string {
128134
t.Helper()
129135

130-
_, port, err := net.SplitHostPort(findListenAddr(t))
131-
require.NoError(t, err)
136+
_, port := findListenAddr(t, "127.0.0.1")
132137

133138
res, err := pool.RunWithOptions(&dockertest.RunOptions{
134139
Repository: "minio/minio",
@@ -172,8 +177,7 @@ var nginxConfigTemplate string
172177
func setupMockCDN(t *testing.T, pool *dockertest.Pool, s3, bucket string) string {
173178
t.Helper()
174179

175-
_, port, err := net.SplitHostPort(findListenAddr(t))
176-
require.NoError(t, err)
180+
_, port := findListenAddr(t, "127.0.0.1")
177181

178182
inlineEntrypoint := fmt.Appendf([]byte{}, nginxConfigTemplate, s3, bucket)
179183

@@ -229,22 +233,26 @@ func setupSecureBoot(t *testing.T, options *cmd.Options) {
229233
}
230234
}
231235

232-
func findListenAddr(t *testing.T) string {
236+
func findListenAddr(t *testing.T, host string) (string, string) {
233237
t.Helper()
234238

235-
l, err := net.Listen("tcp", "127.0.0.1:0")
239+
l, err := net.Listen("tcp", net.JoinHostPort(host, "0"))
236240
require.NoError(t, err)
237241

238242
addr := l.Addr().String()
239243

240244
require.NoError(t, l.Close())
241245

242-
return addr
246+
host, port, err := net.SplitHostPort(addr)
247+
require.NoError(t, err)
248+
249+
return host, port
243250
}
244251

245252
func commonTest(t *testing.T, options cmd.Options) {
246-
ctx, listenAddr := setupFactory(t, options)
253+
ctx, listenAddr, pxeAddr := setupFactory(t, options)
247254
baseURL := "http://" + listenAddr
255+
pxeURL := "http://" + pxeAddr
248256

249257
t.Run("TestSchematic", func(t *testing.T) {
250258
// schematic should be created first, thus no t.Parallel
@@ -260,7 +268,7 @@ func commonTest(t *testing.T, options cmd.Options) {
260268
t.Run("TestPXEFrontend", func(t *testing.T) {
261269
t.Parallel()
262270

263-
testPXEFrontend(ctx, t, baseURL)
271+
testPXEFrontend(ctx, t, baseURL, pxeURL)
264272
})
265273

266274
t.Run("TestTalosctlFrontend", func(t *testing.T) {

internal/integration/pxe_test.go

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
package integration_test
88

99
import (
10+
"bufio"
1011
"context"
1112
"io"
1213
"net/http"
@@ -37,6 +38,33 @@ func downloadPXE(ctx context.Context, t *testing.T, baseURL string, schematicID,
3738
return string(body)
3839
}
3940

41+
func checkPXENoRedirect(ctx context.Context, t *testing.T, pxe string, files ...string) {
42+
t.Helper()
43+
44+
scanner := bufio.NewScanner(strings.NewReader(pxe))
45+
46+
var urls []string
47+
48+
for scanner.Scan() {
49+
line := strings.TrimSpace(scanner.Text())
50+
51+
for _, file := range files {
52+
if strings.HasPrefix(line, file+" ") {
53+
fields := strings.Fields(line)
54+
if len(fields) > 1 {
55+
urls = append(urls, fields[1])
56+
}
57+
}
58+
}
59+
}
60+
61+
require.NoError(t, scanner.Err())
62+
63+
for _, testURL := range urls {
64+
downloadNoRedirect(ctx, t, testURL)
65+
}
66+
}
67+
4068
func fixupCmdline(cmdline string, talosVersion string) string {
4169
if quirks.New(talosVersion).SupportsSELinux() {
4270
cmdline = strings.ReplaceAll(cmdline, "sha512\n", "sha512 selinux=1\n")
@@ -54,7 +82,7 @@ func fixupCmdline(cmdline string, talosVersion string) string {
5482
return cmdline
5583
}
5684

57-
func testPXEFrontend(ctx context.Context, t *testing.T, baseURL string) {
85+
func testPXEFrontend(ctx context.Context, t *testing.T, baseURL, pxeURL string) {
5886
talosVersions := []string{
5987
"v1.5.0",
6088
"v1.11.0-beta.0",
@@ -73,61 +101,77 @@ func testPXEFrontend(ctx context.Context, t *testing.T, baseURL string) {
73101
t.Run("metal-amd64", func(t *testing.T) {
74102
t.Parallel()
75103

104+
pxe := downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "metal-amd64")
105+
76106
assert.Equal(t,
77107
strings.ReplaceAll(
78108
strings.ReplaceAll(
79-
strings.ReplaceAll(fixupCmdline(metalInsecureExpected, talosVersion), "ENDPOINT", baseURL),
109+
strings.ReplaceAll(fixupCmdline(metalInsecureExpected, talosVersion), "ENDPOINT", pxeURL),
80110
"CONFIG", emptySchematicID,
81111
),
82112
"VERSION", talosVersion,
83113
),
84-
downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "metal-amd64"),
114+
pxe,
85115
)
116+
117+
checkPXENoRedirect(ctx, t, pxe, "kernel", "initrd")
86118
})
87119

88120
t.Run("metal-x86_64", func(t *testing.T) {
89121
t.Parallel()
90122

123+
pxe := downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "metal-x86_64")
124+
91125
assert.Equal(t,
92126
strings.ReplaceAll(
93127
strings.ReplaceAll(
94-
strings.ReplaceAll(fixupCmdline(metalInsecureExpected, talosVersion), "ENDPOINT", baseURL),
128+
strings.ReplaceAll(fixupCmdline(metalInsecureExpected, talosVersion), "ENDPOINT", pxeURL),
95129
"CONFIG", emptySchematicID,
96130
),
97131
"VERSION", talosVersion,
98132
),
99-
downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "metal-x86_64"),
133+
pxe,
100134
)
135+
136+
checkPXENoRedirect(ctx, t, pxe, "kernel", "initrd")
101137
})
102138

103139
t.Run("equinix-arm64", func(t *testing.T) {
104140
t.Parallel()
105141

142+
pxe := downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "equinixMetal-amd64")
143+
106144
assert.Equal(t,
107145
strings.ReplaceAll(
108146
strings.ReplaceAll(
109-
strings.ReplaceAll(fixupCmdline(equinixInsecureExpected, talosVersion), "ENDPOINT", baseURL),
147+
strings.ReplaceAll(fixupCmdline(equinixInsecureExpected, talosVersion), "ENDPOINT", pxeURL),
110148
"CONFIG", emptySchematicID,
111149
),
112150
"VERSION", talosVersion,
113151
),
114-
downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "equinixMetal-amd64"),
152+
pxe,
115153
)
154+
155+
checkPXENoRedirect(ctx, t, pxe, "kernel", "initrd")
116156
})
117157

118158
t.Run("secureboot-amd64", func(t *testing.T) {
119159
t.Parallel()
120160

161+
pxe := downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "metal-amd64-secureboot")
162+
121163
assert.Equal(t,
122164
strings.ReplaceAll(
123165
strings.ReplaceAll(
124-
strings.ReplaceAll(fixupCmdline(securebootExpected, talosVersion), "ENDPOINT", baseURL),
166+
strings.ReplaceAll(fixupCmdline(securebootExpected, talosVersion), "ENDPOINT", pxeURL),
125167
"CONFIG", emptySchematicID,
126168
),
127169
"VERSION", talosVersion,
128170
),
129-
downloadPXE(ctx, t, baseURL, emptySchematicID, talosVersion, "metal-amd64-secureboot"),
171+
pxe,
130172
)
173+
174+
checkPXENoRedirect(ctx, t, pxe, "kernel")
131175
})
132176
})
133177
}

0 commit comments

Comments
 (0)