Skip to content

url: support IPv4 and IPv6 for IMDS #2052

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ nav_order: 9

### Features

- IPv6 support for Scaleway metadata endpoint

### Changes

- Rename ignition.cfg -> 05_ignition.cfg
Expand Down
28 changes: 18 additions & 10 deletions internal/providers/scaleway/scaleway.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,28 +24,36 @@ import (

"github.com/coreos/ignition/v2/config/v3_6_experimental/types"
"github.com/coreos/ignition/v2/internal/platform"
"github.com/coreos/ignition/v2/internal/providers/util"
"github.com/coreos/ignition/v2/internal/resource"

"github.com/coreos/vcontext/report"
)

var (
userdataURL = url.URL{
Scheme: "http",
Host: "169.254.42.42",
Path: "user_data/cloud-init",
userdataURLs = map[string]url.URL{
resource.IPv4: {
Scheme: "http",
Host: "169.254.42.42",
Path: "user_data/cloud-init",
},
resource.IPv6: {
Scheme: "http",
Host: "[fd00:42::42]",
Path: "user_data/cloud-init",
},
}
)

func init() {
platform.Register(platform.Provider{
Name: "scaleway",
Fetch: fetchConfig,
Name: "scaleway",
Fetch: func(f *resource.Fetcher) (types.Config, report.Report, error) {
return resource.FetchConfigDualStack(f, userdataURLs, fetchConfig)
},
})
}

func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
func fetchConfig(f *resource.Fetcher, userdataURL url.URL) ([]byte, error) {
// For security reason, Scaleway requires to query user data with a source port below 1024.
port := func() int {
return rand.Intn(1022) + 1
Expand All @@ -55,8 +63,8 @@ func fetchConfig(f *resource.Fetcher) (types.Config, report.Report, error) {
LocalPort: port,
})
if err != nil && err != resource.ErrNotFound {
return types.Config{}, report.Report{}, err
return nil, err
}

return util.ParseConfig(f.Logger, data)
return data, nil
}
66 changes: 65 additions & 1 deletion internal/resource/url.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"io"
"net"
"net/http"
"net/netip"
"net/url"
"os"
"strings"
Expand All @@ -36,9 +37,13 @@ import (
configErrors "github.com/coreos/ignition/v2/config/shared/errors"
"github.com/coreos/ignition/v2/internal/log"
"github.com/coreos/ignition/v2/internal/util"
"github.com/coreos/vcontext/report"
"golang.org/x/oauth2/google"
"google.golang.org/api/option"

"github.com/coreos/ignition/v2/config/v3_6_experimental/types"
providersUtil "github.com/coreos/ignition/v2/internal/providers/util"

"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
"github.com/aws/aws-sdk-go/aws"
Expand All @@ -52,6 +57,11 @@ import (
"github.com/vincent-petithory/dataurl"
)

const (
IPv4 = "ipv4"
IPv6 = "ipv6"
)

var (
ErrSchemeUnsupported = errors.New("unsupported source scheme")
ErrPathNotAbsolute = errors.New("path is not absolute")
Expand Down Expand Up @@ -330,10 +340,17 @@ func (f *Fetcher) fetchFromHTTP(u url.URL, dest io.Writer, opts FetchOptions) er
p int
)

host := u.Hostname()
addr, _ := netip.ParseAddr(host)
network := "tcp6"
if addr.Is4() {
network = "tcp4"
}

// Assert that the port is not already used.
for {
p = opts.LocalPort()
l, err := net.Listen("tcp4", fmt.Sprintf(":%d", p))
l, err := net.Listen(network, fmt.Sprintf(":%d", p))
if err != nil && errors.Is(err, syscall.EADDRINUSE) {
continue
} else if err == nil {
Expand Down Expand Up @@ -725,3 +742,50 @@ func (f *Fetcher) parseARN(arnURL string) (string, string, string, string, error
key := strings.Join(urlSplit[1:], "/")
return bucket, key, "", regionHint, nil
}

// FetchConfigDualStack is a function that takes care of fetching Ignition configuration on systems where IPv4 only, IPv6 only or both are available.
func FetchConfigDualStack(f *Fetcher, userdataURLs map[string]url.URL, fetchConfig func(*Fetcher, url.URL) ([]byte, error)) (types.Config, report.Report, error) {
var (
data []byte
err error
)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

success := make(chan string, 1)

fetch := func(ctx context.Context, ip url.URL) {
data, err = fetchConfig(f, ip)
if err != nil {
f.Logger.Err("fetching configuration for %s: %v", ip.String(), err)
cancel()
return
}

success <- ip.String()
}

if ipv4, ok := userdataURLs[IPv4]; ok {
go fetch(ctx, ipv4)
}

if ipv6, ok := userdataURLs[IPv6]; ok {
go fetch(ctx, ipv6)
}

// Wait for one success. (i.e wait for the first configuration to be available)
select {
case ip := <-success:
if ip != "" {
f.Logger.Debug("got configuration from: %s", ip)
return providersUtil.ParseConfig(f.Logger, data)
}
case <-ctx.Done():
f.Logger.Debug("unable to fetch a configuration from endpoint")
return types.Config{}, report.Report{}, err
}

f.Logger.Debug("unable to fetch a configuration from endpoint")
return types.Config{}, report.Report{}, err
}
Loading