mirror of
https://github.com/siderolabs/talos.git
synced 2026-05-05 12:26:21 +02:00
feat: support auth for Image Factory in cluster create
Allows to authenticate to Image Factory (if Image Factory is configured for auth), applies for HTTP downloads (e.g. ISO), and injects registry auth into Talos as well. Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com> (cherry picked from commit c2948cef232f6a175312636369b444124cb995db)
This commit is contained in:
parent
92ca9e16f9
commit
cd317d5330
@ -15,6 +15,7 @@ import (
|
||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops/configmaker/internal/makers"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/bundle"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/configpatcher"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/encoder"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/generate"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/generate/secrets"
|
||||
@ -144,11 +145,11 @@ func TestCommonMaker_MachineConfig(t *testing.T) {
|
||||
cOps := clusterops.GetCommon()
|
||||
m := getInitializedTestMaker(t, cOps)
|
||||
|
||||
assertConfigDefaultness(t, cOps, m)
|
||||
assertConfigDefaultness(t, cOps, m, nil)
|
||||
}
|
||||
|
||||
// assertConfigDefaultness makes sure the maker-generated machine configs are not different from default talos machine configs.
|
||||
func assertConfigDefaultness[ExtraOps any](t *testing.T, cOps clusterops.Common, m makers.Maker[ExtraOps], desiredExtraGenOps ...generate.Option) {
|
||||
func assertConfigDefaultness[ExtraOps any](t *testing.T, cOps clusterops.Common, m makers.Maker[ExtraOps], desiredExtraGenOps []generate.Option, extraPatches ...configpatcher.Patch) {
|
||||
var versionContract *config.VersionContract
|
||||
|
||||
secretsBundle, err := secrets.NewBundle(secrets.NewClock(), versionContract)
|
||||
@ -171,14 +172,22 @@ func assertConfigDefaultness[ExtraOps any](t *testing.T, cOps clusterops.Common,
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, node := range clusterCfgs.ClusterRequest.Nodes {
|
||||
assertMachineConfig(t, in, node)
|
||||
assertMachineConfig(t, in, node, extraPatches...)
|
||||
}
|
||||
}
|
||||
|
||||
func assertMachineConfig(t *testing.T, in *generate.Input, node provision.NodeRequest) {
|
||||
func assertMachineConfig(t *testing.T, in *generate.Input, node provision.NodeRequest, extraPatches ...configpatcher.Patch) {
|
||||
cfgExpected, err := in.Config(node.Type)
|
||||
require.NoError(t, err)
|
||||
|
||||
if len(extraPatches) > 0 {
|
||||
patched, err := configpatcher.Apply(configpatcher.WithConfig(cfgExpected), extraPatches)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfgExpected, err = patched.Config()
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
cfgGot := node.Config
|
||||
|
||||
cfgGot = cfgGot.RedactSecrets("secret")
|
||||
|
||||
@ -29,5 +29,5 @@ func TestDockerMaker_MachineConfig(t *testing.T) {
|
||||
generate.WithAdditionalSubjectAltNames([]string{"talos-api-endpoint.test"}),
|
||||
}
|
||||
|
||||
assertConfigDefaultness(t, cOps, *m.Maker, desiredExtraGenOps...)
|
||||
assertConfigDefaultness(t, cOps, *m.Maker, desiredExtraGenOps)
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import (
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/generate"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/machine"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/block"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/cri"
|
||||
networkcfg "github.com/siderolabs/talos/pkg/machinery/config/types/network"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/v1alpha1"
|
||||
"github.com/siderolabs/talos/pkg/machinery/constants"
|
||||
@ -170,6 +171,21 @@ func (m *Qemu) AddExtraGenOps() error {
|
||||
m.GenOps = slices.Concat(m.GenOps, []generate.Option{generate.WithAdditionalSubjectAltNames(m.Endpoints)})
|
||||
}
|
||||
|
||||
for host, auth := range m.EOps.DownloadHTTPAuth {
|
||||
registryAuthConfig := cri.NewRegistryAuthConfigV1Alpha1(host)
|
||||
registryAuthConfig.RegistryUsername = auth.Username
|
||||
registryAuthConfig.RegistryPassword = auth.Password
|
||||
|
||||
ctr, err := container.New(registryAuthConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.ConfigBundleOps = append(m.ConfigBundleOps,
|
||||
bundle.WithPatch([]configpatcher.Patch{configpatcher.NewStrategicMergePatch(ctr)}),
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -269,7 +285,7 @@ func (m *Qemu) ModifyClusterRequest() error {
|
||||
m.ClusterRequest.Network.NoMasqueradeCIDRs = noMasqueradeCIDRs
|
||||
m.ClusterRequest.Network.DHCPSkipHostname = m.EOps.DHCPSkipHostname
|
||||
m.ClusterRequest.Network.NetworkChaos = m.EOps.NetworkChaos
|
||||
m.ClusterRequest.Network.Jitter = m.EOps.Jjitter
|
||||
m.ClusterRequest.Network.Jitter = m.EOps.Jitter
|
||||
m.ClusterRequest.Network.Latency = m.EOps.Latency
|
||||
m.ClusterRequest.Network.PacketLoss = m.EOps.PacketLoss
|
||||
m.ClusterRequest.Network.PacketReorder = m.EOps.PacketReorder
|
||||
@ -294,7 +310,7 @@ func (m *Qemu) ModifyClusterRequest() error {
|
||||
|
||||
func (m *Qemu) validateNetworkChaosParams() error {
|
||||
if !m.EOps.NetworkChaos {
|
||||
if m.EOps.Jjitter != 0 || m.EOps.Latency != 0 || m.EOps.PacketLoss != 0 || m.EOps.PacketReorder != 0 || m.EOps.PacketCorrupt != 0 || m.EOps.Bandwidth != 0 {
|
||||
if m.EOps.Jitter != 0 || m.EOps.Latency != 0 || m.EOps.PacketLoss != 0 || m.EOps.PacketReorder != 0 || m.EOps.PacketCorrupt != 0 || m.EOps.Bandwidth != 0 {
|
||||
return errors.New("network chaos flags can only be used with network-chaos option enabled")
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,7 +13,9 @@ import (
|
||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops"
|
||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/clusterops/configmaker/internal/makers"
|
||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster/create/flags"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/generate"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/configpatcher"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/container"
|
||||
"github.com/siderolabs/talos/pkg/machinery/config/types/cri"
|
||||
"github.com/siderolabs/talos/pkg/provision"
|
||||
)
|
||||
|
||||
@ -28,9 +30,35 @@ func TestQemuMaker_MachineConfig(t *testing.T) {
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
desiredExtraGenOps := []generate.Option{}
|
||||
assertConfigDefaultness(t, cOps, *m.Maker, nil)
|
||||
}
|
||||
|
||||
assertConfigDefaultness(t, cOps, *m.Maker, desiredExtraGenOps...)
|
||||
func TestQemuMaker_RegistryAuth(t *testing.T) {
|
||||
cOps := clusterops.GetCommon()
|
||||
qOps := clusterops.GetQemu()
|
||||
|
||||
qOps.DownloadHTTPAuth = map[string]clusterops.HTTPAuth{
|
||||
"example.com": {
|
||||
Username: "username",
|
||||
Password: "password",
|
||||
},
|
||||
}
|
||||
|
||||
m, err := makers.NewQemu(makers.MakerOptions[clusterops.Qemu]{
|
||||
ExtraOps: qOps,
|
||||
CommonOps: cOps,
|
||||
Provisioner: testProvisioner{}, // use test provisioner to simplify the test case.
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
registryAuthConfig := cri.NewRegistryAuthConfigV1Alpha1("example.com")
|
||||
registryAuthConfig.RegistryUsername = "username"
|
||||
registryAuthConfig.RegistryPassword = "password"
|
||||
|
||||
ctr, err := container.New(registryAuthConfig)
|
||||
require.NoError(t, err)
|
||||
|
||||
assertConfigDefaultness(t, cOps, *m.Maker, nil, configpatcher.NewStrategicMergePatch(ctr))
|
||||
}
|
||||
|
||||
func TestQemuMaker_Disks(t *testing.T) {
|
||||
|
||||
@ -129,7 +129,7 @@ type Qemu struct {
|
||||
ExtraBootKernelArgs string
|
||||
DHCPSkipHostname bool
|
||||
NetworkChaos bool
|
||||
Jjitter time.Duration
|
||||
Jitter time.Duration
|
||||
Latency time.Duration
|
||||
PacketLoss float64
|
||||
PacketReorder float64
|
||||
@ -146,6 +146,21 @@ type Qemu struct {
|
||||
ImageCacheTLSCertFile string
|
||||
ImageCacheTLSKeyFile string
|
||||
ImageCachePort uint16
|
||||
|
||||
// DownloadHTTPAuth is a map of endpoint hosts to basic auth credentials used for
|
||||
// HTTP boot asset downloads and for injecting CRI registry auth into generated
|
||||
// Talos config.
|
||||
//
|
||||
// The key is url.URL.Host (that is, "host[:port]", for example
|
||||
// "example.com" or "registry.example.com:5000"), and the value is the HTTPAuth
|
||||
// containing the username and password for that endpoint.
|
||||
DownloadHTTPAuth map[string]HTTPAuth
|
||||
}
|
||||
|
||||
// HTTPAuth represents basic authentication credentials for downloading boot assets.
|
||||
type HTTPAuth struct {
|
||||
Username string
|
||||
Password string
|
||||
}
|
||||
|
||||
// GetCommon returns the default common options.
|
||||
|
||||
@ -242,7 +242,7 @@ func getCreateCmd(cmdName string, hidden bool) *cobra.Command {
|
||||
qemu.StringVar(&qOps.ExtraBootKernelArgs, extraBootKernelArgsFlag, qOps.ExtraBootKernelArgs, "add extra kernel args to the initial boot from vmlinuz and initramfs")
|
||||
qemu.BoolVar(&qOps.DHCPSkipHostname, dhcpSkipHostnameFlag, qOps.DHCPSkipHostname, "skip announcing hostname via DHCP")
|
||||
qemu.BoolVar(&qOps.NetworkChaos, networkChaosFlag, qOps.NetworkChaos, "enable to use network chaos parameters")
|
||||
qemu.DurationVar(&qOps.Jjitter, jitterFlag, qOps.Jjitter, "specify jitter on the bridge interface")
|
||||
qemu.DurationVar(&qOps.Jitter, jitterFlag, qOps.Jitter, "specify jitter on the bridge interface")
|
||||
qemu.DurationVar(&qOps.Latency, latencyFlag, qOps.Latency, "specify latency on the bridge interface")
|
||||
qemu.Float64Var(&qOps.PacketLoss, packetLossFlag, qOps.PacketLoss,
|
||||
"specify percent of packet loss on the bridge interface. e.g. 50% = 0.50 (default: 0.0)")
|
||||
|
||||
@ -19,9 +19,10 @@ import (
|
||||
)
|
||||
|
||||
type presetOptions struct {
|
||||
schematicID string
|
||||
imageFactoryURL string
|
||||
presets []string
|
||||
schematicID string
|
||||
imageFactoryURL string
|
||||
imageFactoryAuth string
|
||||
presets []string
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -40,8 +41,9 @@ func init() {
|
||||
qemu := pflag.NewFlagSet("qemu", pflag.PanicOnError)
|
||||
|
||||
addDisksFlag(qemu, &qOps.Disks)
|
||||
qemu.StringVar(&presetOptions.schematicID, "schematic-id", "", "image factory schematic id (defaults to an empty schematic)")
|
||||
qemu.StringVar(&presetOptions.imageFactoryURL, "image-factory-url", constants.ImageFactoryURL, "image factory url")
|
||||
qemu.StringVar(&presetOptions.schematicID, "schematic-id", "", "Image Factory schematic id (defaults to an empty schematic)")
|
||||
qemu.StringVar(&presetOptions.imageFactoryURL, "image-factory-url", constants.ImageFactoryURL, "Image Factory url")
|
||||
qemu.StringVar(&presetOptions.imageFactoryAuth, "image-factory-auth", "", "username:password for authenticating with the Image Factory")
|
||||
qemu.StringSliceVar(&presetOptions.presets, "presets", []string{preset.ISO{}.Name()}, "list of presets to apply")
|
||||
|
||||
return qemu
|
||||
|
||||
@ -108,6 +108,10 @@ func downloadBootAssets(ctx context.Context, qOps *clusterops.Qemu) error {
|
||||
u.RawQuery = q.Encode()
|
||||
}
|
||||
|
||||
if auth, ok := qOps.DownloadHTTPAuth[u.Host]; ok && u.User == nil {
|
||||
u.User = url.UserPassword(auth.Username, auth.Password)
|
||||
}
|
||||
|
||||
_, err = client.Get(ctx, &getter.Request{
|
||||
Src: u.String(),
|
||||
Dst: filepath.Join(cacheDir, destPath),
|
||||
|
||||
@ -11,6 +11,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/siderolabs/talos/cmd/talosctl/cmd/constants"
|
||||
clustercmd "github.com/siderolabs/talos/cmd/talosctl/cmd/mgmt/cluster"
|
||||
@ -67,6 +68,22 @@ func createQemuCluster(
|
||||
return err
|
||||
}
|
||||
|
||||
if presetOptions.imageFactoryAuth != "" {
|
||||
username, password, ok := strings.Cut(presetOptions.imageFactoryAuth, ":")
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid Image Factory auth format: expected username:password")
|
||||
}
|
||||
|
||||
if qOps.DownloadHTTPAuth == nil {
|
||||
qOps.DownloadHTTPAuth = make(map[string]clusterops.HTTPAuth)
|
||||
}
|
||||
|
||||
qOps.DownloadHTTPAuth[factoryURL.Host] = clusterops.HTTPAuth{
|
||||
Username: username,
|
||||
Password: password,
|
||||
}
|
||||
}
|
||||
|
||||
if err := downloadBootAssets(ctx, &qOps); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -309,13 +309,14 @@ talosctl cluster create qemu [flags]
|
||||
--cpus-workers string the share of CPUs as fraction for each worker/VM (default "2.0")
|
||||
--disks disks list of disks to create in format "<driver1>:<size1>" (disks after the first one are added only to worker machines) (default virtio:10GiB,virtio:6GiB)
|
||||
-h, --help help for qemu
|
||||
--image-factory-url string image factory url (default "https://factory.talos.dev/")
|
||||
--image-factory-auth string username:password for authenticating with the Image Factory
|
||||
--image-factory-url string Image Factory url (default "https://factory.talos.dev/")
|
||||
--kubernetes-version string desired kubernetes version to run (default "1.36.0")
|
||||
--memory-controlplanes string(mb,gb) the limit on memory usage for each control plane/VM (default 2.0GiB)
|
||||
--memory-workers string(mb,gb) the limit on memory usage for each worker/VM (default 2.0GiB)
|
||||
--omni-api-endpoint string the Omni API endpoint (must include a scheme, a hostname and a join token, e.g. 'https://siderolink.omni.example?jointoken=foobar')
|
||||
--presets strings list of presets to apply (default [iso])
|
||||
--schematic-id string image factory schematic id (defaults to an empty schematic)
|
||||
--schematic-id string Image Factory schematic id (defaults to an empty schematic)
|
||||
--talos-version string the desired talos version (default "latest")
|
||||
--talosconfig-destination string The location to save the generated Talos configuration file to. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
--workers int the number of workers to create (default 1)
|
||||
@ -523,13 +524,14 @@ talosctl cluster create qemu [flags]
|
||||
--cpus-workers string the share of CPUs as fraction for each worker/VM (default "2.0")
|
||||
--disks disks list of disks to create in format "<driver1>:<size1>" (disks after the first one are added only to worker machines) (default virtio:10GiB,virtio:6GiB)
|
||||
-h, --help help for qemu
|
||||
--image-factory-url string image factory url (default "https://factory.talos.dev/")
|
||||
--image-factory-auth string username:password for authenticating with the Image Factory
|
||||
--image-factory-url string Image Factory url (default "https://factory.talos.dev/")
|
||||
--kubernetes-version string desired kubernetes version to run (default "1.36.0")
|
||||
--memory-controlplanes string(mb,gb) the limit on memory usage for each control plane/VM (default 2.0GiB)
|
||||
--memory-workers string(mb,gb) the limit on memory usage for each worker/VM (default 2.0GiB)
|
||||
--omni-api-endpoint string the Omni API endpoint (must include a scheme, a hostname and a join token, e.g. 'https://siderolink.omni.example?jointoken=foobar')
|
||||
--presets strings list of presets to apply (default [iso])
|
||||
--schematic-id string image factory schematic id (defaults to an empty schematic)
|
||||
--schematic-id string Image Factory schematic id (defaults to an empty schematic)
|
||||
--talos-version string the desired talos version (default "latest")
|
||||
--talosconfig-destination string The location to save the generated Talos configuration file to. Defaults to 'TALOSCONFIG' env variable if set, otherwise '$HOME/.talos/config' and '/var/run/secrets/talos.dev/config' in order.
|
||||
--workers int the number of workers to create (default 1)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user