Skip to content

Commit 76ecdac

Browse files
yashutanusmira
authored andcommitted
feat: support nocloud include url userdata directive
This commit supports #include directive format of standard cloudinit userdata to fetch userdata/machine config from the remote URL securely. Signed-off-by: yashutanu <yashutanu1@gmail.com> Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit 60c12ba)
1 parent 4f96f35 commit 76ecdac

3 files changed

Lines changed: 114 additions & 1 deletion

File tree

internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/metadata.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,10 @@
66
package nocloud
77

88
import (
9+
"bufio"
10+
"bytes"
911
"context"
12+
stderrors "errors"
1013
"fmt"
1114
"log"
1215
"net"
@@ -933,3 +936,52 @@ func withDefault[T comparable](v T, defaultValue T) T {
933936

934937
return v
935938
}
939+
940+
// FetchInclude fetches nocloud #include configuration from the URL specified in the body.
941+
func (n *Nocloud) FetchInclude(ctx context.Context, body []byte, st state.State) ([]byte, error) {
942+
u, err := ExtractIncludeURL(body)
943+
if err != nil {
944+
return nil, err
945+
}
946+
947+
log.Printf("fetching the nocloud #include configuration from: %q", u.String())
948+
949+
if err = netutils.Wait(ctx, st); err != nil {
950+
return nil, err
951+
}
952+
953+
return download.Download(ctx, u.String(), download.WithErrorOnNotFound(errors.ErrNoConfigSource),
954+
download.WithErrorOnEmptyResponse(errors.ErrNoConfigSource))
955+
}
956+
957+
// ExtractIncludeURL extracts the URL from the body of a nocloud #include configuration.
958+
//
959+
// Note: only a single URL is expected in the body.
960+
func ExtractIncludeURL(body []byte) (*url.URL, error) {
961+
var urlLine string
962+
963+
scanner := bufio.NewScanner(bytes.NewReader(body))
964+
965+
for scanner.Scan() {
966+
line := strings.TrimSpace(scanner.Text())
967+
if line == "" {
968+
continue
969+
}
970+
971+
if urlLine != "" {
972+
return nil, fmt.Errorf("multiple #include URLs found in nocloud configuration: %q and %q", urlLine, line)
973+
}
974+
975+
urlLine = line
976+
}
977+
978+
if err := scanner.Err(); err != nil {
979+
return nil, err
980+
}
981+
982+
if urlLine == "" {
983+
return nil, stderrors.New("no #include URL found in nocloud configuration")
984+
}
985+
986+
return url.Parse(urlLine)
987+
}

internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/nocloud.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,15 @@ func (n *Nocloud) Configuration(ctx context.Context, r state.State) ([]byte, err
9696
return nil, err
9797
}
9898

99-
if bytes.HasPrefix(machineConfigDl, []byte("#cloud-config")) {
99+
firstLine, rest, _ := bytes.Cut(machineConfigDl, []byte("\n"))
100+
firstLine = bytes.TrimSpace(firstLine)
101+
102+
switch {
103+
case bytes.Equal(firstLine, []byte("#cloud-config")):
104+
// ignore cloud-config, Talos does not support it
100105
return nil, errors.ErrNoConfigSource
106+
case bytes.Equal(firstLine, []byte("#include")):
107+
return n.FetchInclude(ctx, rest, r)
101108
}
102109

103110
return machineConfigDl, nil

internal/app/machined/pkg/runtime/v1alpha1/platform/nocloud/nocloud_test.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,3 +189,57 @@ func TestMedatada(t *testing.T) {
189189
InternalDNS: "talos-worker-3",
190190
}, md)
191191
}
192+
193+
func TestExtractURL(t *testing.T) {
194+
t.Parallel()
195+
196+
for _, test := range []struct {
197+
name string
198+
userdata []byte
199+
200+
expectedURL string
201+
expectedError string
202+
}{
203+
{
204+
name: "valid include userdata URL",
205+
userdata: []byte(`https://metadataserver/userdata
206+
`),
207+
expectedURL: "https://metadataserver/userdata",
208+
},
209+
{
210+
name: "multiple URLs is invalid",
211+
userdata: []byte(`
212+
https://metadataserver1/userdata
213+
https://metadataserver2/userdata
214+
https://metadataserver3/userdata
215+
`),
216+
expectedError: "multiple #include URLs found",
217+
},
218+
{
219+
name: "invalid URL",
220+
userdata: []byte(`
221+
:/invalidurl/userdata`),
222+
expectedError: "missing protocol scheme",
223+
},
224+
{
225+
name: "no URL found",
226+
userdata: []byte(`
227+
`),
228+
expectedError: "no #include URL found in nocloud configuration",
229+
},
230+
} {
231+
t.Run(test.name, func(t *testing.T) {
232+
t.Parallel()
233+
234+
u, err := nocloud.ExtractIncludeURL(test.userdata)
235+
236+
if test.expectedError != "" {
237+
require.Error(t, err)
238+
require.ErrorContains(t, err, test.expectedError)
239+
} else {
240+
require.NoError(t, err)
241+
assert.Equal(t, test.expectedURL, u.String())
242+
}
243+
})
244+
}
245+
}

0 commit comments

Comments
 (0)