feat: implement registry mirror & config for image pull

When images are pulled by Talos or via CRI plugin, configuration
for each registry is applied. Mirrors allow to redirect pull request to
either local registry or cached registry. Auth & TLS enable
authentication and TLS authentication for non-public registries.

Signed-off-by: Andrey Smirnov <smirnov.andrey@gmail.com>
This commit is contained in:
Andrey Smirnov 2020-02-07 23:39:13 +03:00 committed by Andrey Smirnov
parent 33332f4c74
commit e1779ac77c
25 changed files with 991 additions and 15 deletions

View File

@ -13,6 +13,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"text/tabwriter" "text/tabwriter"
"time" "time"
@ -37,6 +38,7 @@ var (
clusterName string clusterName string
nodeImage string nodeImage string
nodeInstallImage string nodeInstallImage string
registryMirrors []string
nodeVmlinuxPath string nodeVmlinuxPath string
nodeInitramfsPath string nodeInitramfsPath string
bootloaderEmulation bool bootloaderEmulation bool
@ -196,6 +198,15 @@ func create(ctx context.Context) (err error) {
generate.WithInstallImage(nodeInstallImage), generate.WithInstallImage(nodeInstallImage),
} }
for _, registryMirror := range registryMirrors {
components := strings.SplitN(registryMirror, "=", 2)
if len(components) != 2 {
return fmt.Errorf("invalid registry mirror spec: %q", registryMirror)
}
genOptions = append(genOptions, generate.WithRegistryMirror(components[0], components[1]))
}
genOptions = append(genOptions, provisioner.GenOptions(request.Network)...) genOptions = append(genOptions, provisioner.GenOptions(request.Network)...)
defaultInternalLB, defaultExternalLB := provisioner.GetLoadBalancers(request.Network) defaultInternalLB, defaultExternalLB := provisioner.GetLoadBalancers(request.Network)
@ -418,6 +429,7 @@ func init() {
clusterUpCmd.Flags().StringVar(&nodeVmlinuxPath, "vmlinux-path", helpers.ArtifactPath(constants.KernelUncompressedAsset), "the uncompressed kernel image to use") clusterUpCmd.Flags().StringVar(&nodeVmlinuxPath, "vmlinux-path", helpers.ArtifactPath(constants.KernelUncompressedAsset), "the uncompressed kernel image to use")
clusterUpCmd.Flags().StringVar(&nodeInitramfsPath, "initrd-path", helpers.ArtifactPath(constants.InitramfsAsset), "the uncompressed kernel image to use") clusterUpCmd.Flags().StringVar(&nodeInitramfsPath, "initrd-path", helpers.ArtifactPath(constants.InitramfsAsset), "the uncompressed kernel image to use")
clusterUpCmd.Flags().BoolVar(&bootloaderEmulation, "with-bootloader-emulation", false, "enable bootloader emulation to load kernel and initramfs from disk image") clusterUpCmd.Flags().BoolVar(&bootloaderEmulation, "with-bootloader-emulation", false, "enable bootloader emulation to load kernel and initramfs from disk image")
clusterUpCmd.Flags().StringSliceVar(&registryMirrors, "registry-mirror", []string{}, "list of registry mirrors to use in format: <registry host>=<mirror URL>")
clusterUpCmd.Flags().IntVar(&networkMTU, "mtu", 1500, "MTU of the docker bridge network") clusterUpCmd.Flags().IntVar(&networkMTU, "mtu", 1500, "MTU of the docker bridge network")
clusterUpCmd.Flags().StringVar(&networkCIDR, "cidr", "10.5.0.0/24", "CIDR of the docker bridge network") clusterUpCmd.Flags().StringVar(&networkCIDR, "cidr", "10.5.0.0/24", "CIDR of the docker bridge network")
clusterUpCmd.Flags().StringSliceVar(&nameservers, "nameservers", []string{"8.8.8.8", "1.1.1.1"}, "list of nameservers to use (VM only)") clusterUpCmd.Flags().StringSliceVar(&nameservers, "nameservers", []string{"8.8.8.8", "1.1.1.1"}, "list of nameservers to use (VM only)")

View File

@ -201,17 +201,28 @@ func genV1Alpha1Config(args []string) error {
return fmt.Errorf("failed to create output dir: %w", err) return fmt.Errorf("failed to create output dir: %w", err)
} }
var genOptions []generate.GenOption //nolint: prealloc
for _, registryMirror := range registryMirrors {
components := strings.SplitN(registryMirror, "=", 2)
if len(components) != 2 {
return fmt.Errorf("invalid registry mirror spec: %q", registryMirror)
}
genOptions = append(genOptions, generate.WithRegistryMirror(components[0], components[1]))
}
configBundle, err := config.NewConfigBundle( configBundle, err := config.NewConfigBundle(
config.WithInputOptions( config.WithInputOptions(
&config.InputOptions{ &config.InputOptions{
ClusterName: args[0], ClusterName: args[0],
Endpoint: args[1], Endpoint: args[1],
KubeVersion: kubernetesVersion, KubeVersion: kubernetesVersion,
GenOptions: []generate.GenOption{ GenOptions: append(genOptions,
generate.WithInstallDisk(installDisk), generate.WithInstallDisk(installDisk),
generate.WithInstallImage(installImage), generate.WithInstallImage(installImage),
generate.WithAdditionalSubjectAltNames(additionalSANs), generate.WithAdditionalSubjectAltNames(additionalSANs),
}, ),
}, },
), ),
) )
@ -280,6 +291,7 @@ func init() {
configGenerateCmd.Flags().StringVar(&configVersion, "version", "v1alpha1", "the desired machine config version to generate") configGenerateCmd.Flags().StringVar(&configVersion, "version", "v1alpha1", "the desired machine config version to generate")
configGenerateCmd.Flags().StringVar(&kubernetesVersion, "kubernetes-version", constants.DefaultKubernetesVersion, "desired kubernetes version to run") configGenerateCmd.Flags().StringVar(&kubernetesVersion, "kubernetes-version", constants.DefaultKubernetesVersion, "desired kubernetes version to run")
configGenerateCmd.Flags().StringVarP(&outputDir, "output-dir", "o", "", "destination to output generated files") configGenerateCmd.Flags().StringVarP(&outputDir, "output-dir", "o", "", "destination to output generated files")
configGenerateCmd.Flags().StringSliceVar(&registryMirrors, "registry-mirror", []string{}, "list of registry mirrors to use in format: <registry host>=<mirror URL>")
helpers.Should(configAddCmd.MarkFlagRequired("ca")) helpers.Should(configAddCmd.MarkFlagRequired("ca"))
helpers.Should(configAddCmd.MarkFlagRequired("crt")) helpers.Should(configAddCmd.MarkFlagRequired("crt"))
helpers.Should(configAddCmd.MarkFlagRequired("key")) helpers.Should(configAddCmd.MarkFlagRequired("key"))

View File

@ -32,6 +32,7 @@ osctl cluster create [flags]
--memory int the limit on memory usage in MB (each container) (default 1024) --memory int the limit on memory usage in MB (each container) (default 1024)
--mtu int MTU of the docker bridge network (default 1500) --mtu int MTU of the docker bridge network (default 1500)
--nameservers strings list of nameservers to use (VM only) (default [8.8.8.8,1.1.1.1]) --nameservers strings list of nameservers to use (VM only) (default [8.8.8.8,1.1.1.1])
--registry-mirror strings list of registry mirrors to use in format: <registry host>=<mirror URL>
--vmlinux-path string the uncompressed kernel image to use (default "_out/vmlinux") --vmlinux-path string the uncompressed kernel image to use (default "_out/vmlinux")
--wait wait for the cluster to be ready before returning --wait wait for the cluster to be ready before returning
--wait-timeout duration timeout to wait for the cluster to be ready (default 20m0s) --wait-timeout duration timeout to wait for the cluster to be ready (default 20m0s)

View File

@ -20,6 +20,7 @@ osctl config generate <cluster name> https://<load balancer IP or DNS name> [fla
--install-image string the image used to perform an installation (default "docker.io/autonomy/installer:latest") --install-image string the image used to perform an installation (default "docker.io/autonomy/installer:latest")
--kubernetes-version string desired kubernetes version to run (default "1.17.1") --kubernetes-version string desired kubernetes version to run (default "1.17.1")
-o, --output-dir string destination to output generated files -o, --output-dir string destination to output generated files
--registry-mirror strings list of registry mirrors to use in format: <registry host>=<mirror URL>
--version string the desired machine config version to generate (default "v1alpha1") --version string the desired machine config version to generate (default "v1alpha1")
``` ```

View File

@ -331,6 +331,49 @@ sysctls:
``` ```
#### registries
Used to configure the machine's container image registry mirrors.
Automatically generates matching CRI configuration for registry mirrors.
Section `mirrors` allows to redirect requests for images to non-default registry,
which might be local registry or caching mirror.
Section `config` provides a way to authenticate to the registry with TLS client
identity, provide registry CA, or authentication information.
Authentication information has same meaning with the corresponding field in `.docker/config.json`.
See also matching configuration for [CRI containerd plugin](https://github.com/containerd/cri/blob/master/docs/registry.md).
Type: `RegistriesConfig`
Examples:
```yaml
registries:
mirrors:
docker.io:
endpoints:
- https://registry-1.docker.io
'*':
endpoints:
- http://some.host:123/
config:
"some.host:123":
tls:
CA: ... # base64-encoded CA certificate in PEM format
clientIdentity:
cert: ... # base64-encoded client certificate in PEM format
key: ... # base64-encoded client key in PEM format
auth:
username: ...
password: ...
auth: ...
identityToken: ...
```
--- ---
### ClusterConfig ### ClusterConfig
@ -739,6 +782,32 @@ Type: `array`
--- ---
### RegistriesConfig
#### mirrors
Specifies mirror configuration for each registry.
This setting allows to use local pull-through caching registires,
air-gapped installations, etc.
Registry name is the first segment of image identifier, with 'docker.io'
being default one.
Name '*' catches any registry names not specified explicitly.
Type: `map`
#### config
Specifies TLS & auth configuration for HTTPS image registries.
Mutual TLS can be enabled with 'clientIdentity' option.
TLS configuration can be skipped if registry has trusted
server certificate.
Type: `map`
---
### PodCheckpointer ### PodCheckpointer
#### image #### image

1
go.mod
View File

@ -10,6 +10,7 @@ replace (
require ( require (
code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c code.cloudfoundry.org/bytefmt v0.0.0-20180906201452-2aa6f33b730c
github.com/BurntSushi/toml v0.3.1
github.com/Microsoft/hcsshim v0.8.7 // indirect github.com/Microsoft/hcsshim v0.8.7 // indirect
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e
github.com/beevik/ntp v0.2.0 github.com/beevik/ntp v0.2.0

View File

@ -42,6 +42,7 @@ import (
"github.com/talos-systems/talos/pkg/chunker" "github.com/talos-systems/talos/pkg/chunker"
filechunker "github.com/talos-systems/talos/pkg/chunker/file" filechunker "github.com/talos-systems/talos/pkg/chunker/file"
"github.com/talos-systems/talos/pkg/chunker/stream" "github.com/talos-systems/talos/pkg/chunker/stream"
machinecfg "github.com/talos-systems/talos/pkg/config/machine"
"github.com/talos-systems/talos/pkg/constants" "github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/version" "github.com/talos-systems/talos/pkg/version"
) )
@ -105,7 +106,7 @@ func (r *Registrator) Shutdown(ctx context.Context, in *empty.Empty) (reply *mac
// Upgrade initiates an upgrade. // Upgrade initiates an upgrade.
func (r *Registrator) Upgrade(ctx context.Context, in *machineapi.UpgradeRequest) (data *machineapi.UpgradeResponse, err error) { func (r *Registrator) Upgrade(ctx context.Context, in *machineapi.UpgradeRequest) (data *machineapi.UpgradeResponse, err error) {
if err = pullAndValidateInstallerImage(ctx, in.GetImage()); err != nil { if err = pullAndValidateInstallerImage(ctx, r.config.Machine().Registries(), in.GetImage()); err != nil {
return nil, err return nil, err
} }
@ -574,7 +575,7 @@ func (r *Registrator) Read(in *machineapi.ReadRequest, srv machineapi.MachineSer
} }
} }
func pullAndValidateInstallerImage(ctx context.Context, ref string) error { func pullAndValidateInstallerImage(ctx context.Context, config machinecfg.Registries, ref string) error {
// Pull down specified installer image early so we can bail if it doesn't exist in the upstream registry // Pull down specified installer image early so we can bail if it doesn't exist in the upstream registry
containerdctx := namespaces.WithNamespace(ctx, constants.SystemContainerdNamespace) containerdctx := namespaces.WithNamespace(ctx, constants.SystemContainerdNamespace)
@ -583,7 +584,7 @@ func pullAndValidateInstallerImage(ctx context.Context, ref string) error {
return err return err
} }
img, err := image.Pull(containerdctx, client, ref) img, err := image.Pull(containerdctx, config, client, ref)
if err != nil { if err != nil {
return err return err
} }

View File

@ -35,7 +35,12 @@ func (task *ExtraFiles) TaskFunc(mode runtime.Mode) phase.TaskFunc {
func (task *ExtraFiles) runtime(r runtime.Runtime) (err error) { func (task *ExtraFiles) runtime(r runtime.Runtime) (err error) {
var result *multierror.Error var result *multierror.Error
for _, f := range r.Config().Machine().Files() { files, err := r.Config().Machine().Files()
if err != nil {
return fmt.Errorf("error generating extra files: %w", err)
}
for _, f := range files {
content := f.Content content := f.Content
switch f.Op { switch f.Op {

View File

@ -69,7 +69,7 @@ func (e *Etcd) PreFunc(ctx context.Context, config runtime.Configurator) (err er
// Pull the image and unpack it. // Pull the image and unpack it.
containerdctx := namespaces.WithNamespace(ctx, constants.SystemContainerdNamespace) containerdctx := namespaces.WithNamespace(ctx, constants.SystemContainerdNamespace)
if _, err = image.Pull(containerdctx, client, config.Cluster().Etcd().Image()); err != nil { if _, err = image.Pull(containerdctx, config.Machine().Registries(), client, config.Cluster().Etcd().Image()); err != nil {
return fmt.Errorf("failed to pull image %q: %w", config.Cluster().Etcd().Image(), err) return fmt.Errorf("failed to pull image %q: %w", config.Cluster().Etcd().Image(), err)
} }

View File

@ -114,7 +114,7 @@ func (k *Kubelet) PreFunc(ctx context.Context, config runtime.Configurator) erro
// Pull the image and unpack it. // Pull the image and unpack it.
containerdctx := namespaces.WithNamespace(ctx, "k8s.io") containerdctx := namespaces.WithNamespace(ctx, "k8s.io")
_, err = image.Pull(containerdctx, client, config.Machine().Kubelet().Image()) _, err = image.Pull(containerdctx, config.Machine().Registries(), client, config.Machine().Kubelet().Image())
if err != nil { if err != nil {
return err return err
} }

View File

@ -0,0 +1,153 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package containerd
import (
"bytes"
"fmt"
"path/filepath"
"github.com/BurntSushi/toml"
"github.com/talos-systems/talos/pkg/config/machine"
"github.com/talos-systems/talos/pkg/constants"
)
// config structures to generate TOML containerd CRI plugin config
type mirror struct {
Endpoints []string `toml:"endpoint"`
}
type authConfig struct {
Username string `toml:"username"`
Password string `toml:"password"`
Auth string `toml:"auth"`
IdentityToken string `toml:"identitytoken"`
}
type tlsConfig struct {
InsecureSkipVerify bool `toml:"insecure_skip_verify"`
CAFile string `toml:"ca_file"`
CertFile string `toml:"cert_file"`
KeyFile string `toml:"key_file"`
}
type registryConfig struct {
Auth *authConfig `toml:"auth"`
TLS *tlsConfig `toml:"tls"`
}
type registry struct {
Mirrors map[string]mirror `toml:"mirrors"`
Configs map[string]registryConfig `toml:"configs"`
}
type criConfig struct {
Registry registry `toml:"registry"`
}
type pluginsConfig struct {
CRI criConfig `toml:"cri"`
}
type containerdConfig struct {
Plugins pluginsConfig `toml:"plugins"`
}
// GenerateRegistriesConfig for containerd CRI plugin (TOML format).
//
//nolint: gocyclo
func GenerateRegistriesConfig(input machine.Registries) ([]machine.File, error) {
caPath := filepath.Join(filepath.Dir(constants.CRIContainerdConfig), "ca")
clientPath := filepath.Join(filepath.Dir(constants.CRIContainerdConfig), "client")
var config containerdConfig
config.Plugins.CRI.Registry.Mirrors = make(map[string]mirror)
config.Plugins.CRI.Registry.Configs = make(map[string]registryConfig)
for mirrorName, mirrorConfig := range input.Mirrors() {
config.Plugins.CRI.Registry.Mirrors[mirrorName] = mirror{Endpoints: mirrorConfig.Endpoints}
}
var extraFiles []machine.File
for registryHost, hostConfig := range input.Config() {
cfg := registryConfig{}
if hostConfig.Auth != nil {
cfg.Auth = &authConfig{
Username: hostConfig.Auth.Username,
Password: hostConfig.Auth.Password,
Auth: hostConfig.Auth.Auth,
IdentityToken: hostConfig.Auth.IdentityToken,
}
}
if hostConfig.TLS != nil {
cfg.TLS = &tlsConfig{
InsecureSkipVerify: hostConfig.TLS.InsecureSkipVerify,
}
if hostConfig.TLS.CA != nil {
path := filepath.Join(caPath, fmt.Sprintf("%s.crt", registryHost))
extraFiles = append(extraFiles, machine.File{
Content: string(hostConfig.TLS.CA),
Permissions: 0600,
Path: path,
Op: "create",
})
cfg.TLS.CAFile = path
}
if hostConfig.TLS.ClientIdentity.Crt != nil {
path := filepath.Join(clientPath, fmt.Sprintf("%s.crt", registryHost))
extraFiles = append(extraFiles, machine.File{
Content: string(hostConfig.TLS.ClientIdentity.Crt),
Permissions: 0600,
Path: path,
Op: "create",
})
cfg.TLS.CertFile = path
}
if hostConfig.TLS.ClientIdentity.Key != nil {
path := filepath.Join(clientPath, fmt.Sprintf("%s.key", registryHost))
extraFiles = append(extraFiles, machine.File{
Content: string(hostConfig.TLS.ClientIdentity.Key),
Permissions: 0600,
Path: path,
Op: "create",
})
cfg.TLS.KeyFile = path
}
}
if cfg.Auth != nil || cfg.TLS != nil {
config.Plugins.CRI.Registry.Configs[registryHost] = cfg
}
}
var buf bytes.Buffer
if err := toml.NewEncoder(&buf).Encode(&config); err != nil {
return nil, err
}
// CRI plugin doesn't support merging configs for plugins across files,
// so we have to append CRI plugin to the main config, as it already contains
// configuration pieces for CRI plugin
return append(extraFiles, machine.File{
Content: buf.String(),
Permissions: 0644,
Path: constants.CRIContainerdConfig,
Op: "append",
}), nil
}

View File

@ -0,0 +1,117 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package containerd_test
import (
"fmt"
"testing"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/talos/internal/pkg/containers/cri/containerd"
"github.com/talos-systems/talos/pkg/config/machine"
"github.com/talos-systems/talos/pkg/constants"
"github.com/talos-systems/talos/pkg/crypto/x509"
)
type mockConfig struct {
mirrors map[string]machine.RegistryMirrorConfig
config map[string]machine.RegistryConfig
}
func (c *mockConfig) Mirrors() map[string]machine.RegistryMirrorConfig {
return c.mirrors
}
func (c *mockConfig) Config() map[string]machine.RegistryConfig {
return c.config
}
func (c *mockConfig) ExtraFiles() ([]machine.File, error) {
return nil, fmt.Errorf("not implemented")
}
type ConfigSuite struct {
suite.Suite
}
func (suite *ConfigSuite) TestGenerateRegistriesConfig() {
cfg := &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"https://registry-1.docker.io", "https://registry-2.docker.io"},
},
},
config: map[string]machine.RegistryConfig{
"some.host:123": {
Auth: &machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
Auth: "auth",
IdentityToken: "token",
},
TLS: &machine.RegistryTLSConfig{
InsecureSkipVerify: true,
CA: []byte("cacert"),
ClientIdentity: &x509.PEMEncodedCertificateAndKey{
Crt: []byte("clientcert"),
Key: []byte("clientkey"),
},
},
},
},
}
files, err := containerd.GenerateRegistriesConfig(cfg)
suite.Require().NoError(err)
suite.Assert().Equal([]machine.File{
{
Content: `cacert`,
Permissions: 0600,
Path: "/etc/cri/ca/some.host:123.crt",
Op: "create",
},
{
Content: `clientcert`,
Permissions: 0600,
Path: "/etc/cri/client/some.host:123.crt",
Op: "create",
},
{
Content: `clientkey`,
Permissions: 0600,
Path: "/etc/cri/client/some.host:123.key",
Op: "create",
},
{
Content: `[plugins]
[plugins.cri]
[plugins.cri.registry]
[plugins.cri.registry.mirrors]
[plugins.cri.registry.mirrors."docker.io"]
endpoint = ["https://registry-1.docker.io", "https://registry-2.docker.io"]
[plugins.cri.registry.configs]
[plugins.cri.registry.configs."some.host:123"]
[plugins.cri.registry.configs."some.host:123".auth]
username = "root"
password = "secret"
auth = "auth"
identitytoken = "token"
[plugins.cri.registry.configs."some.host:123".tls]
insecure_skip_verify = true
ca_file = "/etc/cri/ca/some.host:123.crt"
cert_file = "/etc/cri/client/some.host:123.crt"
key_file = "/etc/cri/client/some.host:123.key"
`,
Permissions: 0644,
Path: constants.CRIContainerdConfig,
Op: "append",
},
}, files)
}
func TestConfigSuite(t *testing.T) {
suite.Run(t, new(ConfigSuite))
}

View File

@ -0,0 +1,6 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Package containerd provides support for containerd CRI plugin
package containerd

View File

@ -9,6 +9,7 @@ import (
"fmt" "fmt"
"time" "time"
"github.com/talos-systems/talos/pkg/config/machine"
"github.com/talos-systems/talos/pkg/retry" "github.com/talos-systems/talos/pkg/retry"
"github.com/containerd/containerd" "github.com/containerd/containerd"
@ -16,9 +17,11 @@ import (
// Pull is a convenience function that wraps the containerd image pull func with // Pull is a convenience function that wraps the containerd image pull func with
// retry functionality. // retry functionality.
func Pull(ctx context.Context, client *containerd.Client, ref string) (img containerd.Image, err error) { func Pull(ctx context.Context, config machine.Registries, client *containerd.Client, ref string) (img containerd.Image, err error) {
resolver := NewResolver(config)
err = retry.Exponential(1*time.Minute, retry.WithUnits(1*time.Second)).Retry(func() error { err = retry.Exponential(1*time.Minute, retry.WithUnits(1*time.Second)).Retry(func() error {
if img, err = client.Pull(ctx, ref, containerd.WithPullUnpack); err != nil { if img, err = client.Pull(ctx, ref, containerd.WithPullUnpack, containerd.WithResolver(resolver)); err != nil {
return retry.ExpectedError(fmt.Errorf("failed to pull image %q: %w", ref, err)) return retry.ExpectedError(fmt.Errorf("failed to pull image %q: %w", ref, err))
} }

View File

@ -0,0 +1,167 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package image
import (
"encoding/base64"
"fmt"
"net"
"net/http"
"net/url"
"strings"
"time"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
"github.com/talos-systems/talos/pkg/config/machine"
)
// NewResolver builds registry resolver based on Talos configuration.
func NewResolver(config machine.Registries) remotes.Resolver {
return docker.NewResolver(docker.ResolverOptions{
Hosts: RegistryHosts(config),
})
}
// RegistryHosts returns host configuration per registry.
func RegistryHosts(config machine.Registries) docker.RegistryHosts {
return func(host string) ([]docker.RegistryHost, error) {
var registries []docker.RegistryHost
endpoints, err := RegistryEndpoints(config, host)
if err != nil {
return nil, err
}
for _, endpoint := range endpoints {
u, err := url.Parse(endpoint)
if err != nil {
return nil, fmt.Errorf("error parsing endpoint %q for host %q: %w", endpoint, host, err)
}
transport := newTransport()
client := &http.Client{Transport: transport}
registryConfig := config.Config()[u.Host]
if u.Scheme != "https" && registryConfig.TLS != nil {
return nil, fmt.Errorf("TLS config specified for non-HTTPS registry: %q", u.Host)
}
if registryConfig.TLS != nil {
transport.TLSClientConfig, err = registryConfig.TLS.GetTLSConfig()
if err != nil {
return nil, fmt.Errorf("error preparing TLS config for %q: %w", u.Host, err)
}
}
if u.Path == "" {
u.Path = "/v2"
}
uu := u
registries = append(registries, docker.RegistryHost{
Client: client,
Authorizer: docker.NewDockerAuthorizer(
docker.WithAuthClient(client),
docker.WithAuthCreds(func(host string) (string, string, error) {
return PrepareAuth(registryConfig.Auth, uu.Host, host)
})),
Host: uu.Host,
Scheme: uu.Scheme,
Path: uu.Path,
Capabilities: docker.HostCapabilityResolve | docker.HostCapabilityPull,
})
}
return registries, nil
}
}
// RegistryEndpoints returns registry endpoints per host using config.
func RegistryEndpoints(config machine.Registries, host string) ([]string, error) {
var endpoints []string
if hostConfig, ok := config.Mirrors()[host]; ok {
endpoints = hostConfig.Endpoints
}
if endpoints == nil {
if catchAllConfig, ok := config.Mirrors()["*"]; ok {
endpoints = catchAllConfig.Endpoints
}
}
if len(endpoints) == 0 {
// still no endpoints, use default
defaultHost, err := docker.DefaultHost(host)
if err != nil {
return nil, fmt.Errorf("error getting default host for %q: %w", host, err)
}
endpoints = append(endpoints, "https://"+defaultHost)
}
return endpoints, nil
}
// PrepareAuth returns authentication info in the format expected by containerd.
func PrepareAuth(auth *machine.RegistryAuthConfig, host, expectedHost string) (string, string, error) {
if auth == nil {
return "", "", nil
}
if expectedHost != host {
// don't pass auth to unknown hosts
return "", "", nil
}
if auth.Username != "" {
return auth.Username, auth.Password, nil
}
if auth.IdentityToken != "" {
return "", auth.IdentityToken, nil
}
if auth.Auth != "" {
decLen := base64.StdEncoding.DecodedLen(len(auth.Auth))
decoded := make([]byte, decLen)
_, err := base64.StdEncoding.Decode(decoded, []byte(auth.Auth))
if err != nil {
return "", "", fmt.Errorf("error parsing auth for %q: %w", host, err)
}
fields := strings.SplitN(string(decoded), ":", 2)
if len(fields) != 2 {
return "", "", fmt.Errorf("invalid decoded auth for %q: %q", host, decoded)
}
user, password := fields[0], fields[1]
return user, strings.Trim(password, "\x00"), nil
}
return "", "", fmt.Errorf("invalid auth config for %q", host)
}
// newTransport creates HTTP transport with default settings.
func newTransport() *http.Transport {
return &http.Transport{
Proxy: http.ProxyFromEnvironment,
DialContext: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
DualStack: true,
}).DialContext,
MaxIdleConns: 10,
IdleConnTimeout: 30 * time.Second,
TLSHandshakeTimeout: 10 * time.Second,
ExpectContinueTimeout: 5 * time.Second,
}
}

View File

@ -0,0 +1,225 @@
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package image_test
import (
"context"
"fmt"
"net/http"
"testing"
"github.com/stretchr/testify/suite"
"github.com/talos-systems/talos/internal/pkg/containers/image"
"github.com/talos-systems/talos/pkg/config/machine"
)
type mockConfig struct {
mirrors map[string]machine.RegistryMirrorConfig
config map[string]machine.RegistryConfig
}
func (c *mockConfig) Mirrors() map[string]machine.RegistryMirrorConfig {
return c.mirrors
}
func (c *mockConfig) Config() map[string]machine.RegistryConfig {
return c.config
}
func (c *mockConfig) ExtraFiles() ([]machine.File, error) {
return nil, fmt.Errorf("not implemented")
}
type ResolverSuite struct {
suite.Suite
}
func (suite *ResolverSuite) TestRegistryEndpoints() {
// defaults
endpoints, err := image.RegistryEndpoints(&mockConfig{}, "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"https://registry-1.docker.io"}, endpoints)
endpoints, err = image.RegistryEndpoints(&mockConfig{}, "quay.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"https://quay.io"}, endpoints)
// overrides without catch-all
cfg := &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"http://127.0.0.1:5000", "https://some.host"},
},
},
}
endpoints, err = image.RegistryEndpoints(cfg, "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"http://127.0.0.1:5000", "https://some.host"}, endpoints)
endpoints, err = image.RegistryEndpoints(cfg, "quay.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"https://quay.io"}, endpoints)
// overrides with catch-all
cfg = &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"http://127.0.0.1:5000", "https://some.host"},
},
"*": {
Endpoints: []string{"http://127.0.0.1:5001"},
},
},
}
endpoints, err = image.RegistryEndpoints(cfg, "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"http://127.0.0.1:5000", "https://some.host"}, endpoints)
endpoints, err = image.RegistryEndpoints(cfg, "quay.io")
suite.Assert().NoError(err)
suite.Assert().Equal([]string{"http://127.0.0.1:5001"}, endpoints)
}
func (suite *ResolverSuite) TestPrepareAuth() {
user, pass, err := image.PrepareAuth(nil, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("", user)
suite.Assert().Equal("", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
}, "docker.io", "not.docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("", user)
suite.Assert().Equal("", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
}, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("root", user)
suite.Assert().Equal("secret", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
IdentityToken: "xyz",
}, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("", user)
suite.Assert().Equal("xyz", pass)
user, pass, err = image.PrepareAuth(&machine.RegistryAuthConfig{
Auth: "dXNlcjE6c2VjcmV0MQ==",
}, "docker.io", "docker.io")
suite.Assert().NoError(err)
suite.Assert().Equal("user1", user)
suite.Assert().Equal("secret1", pass)
_, _, err = image.PrepareAuth(&machine.RegistryAuthConfig{}, "docker.io", "docker.io")
suite.Assert().EqualError(err, "invalid auth config for \"docker.io\"")
}
func (suite *ResolverSuite) TestRegistryHosts() {
registryHosts, err := image.RegistryHosts(&mockConfig{})("docker.io")
suite.Require().NoError(err)
suite.Assert().Len(registryHosts, 1)
suite.Assert().Equal("https", registryHosts[0].Scheme)
suite.Assert().Equal("registry-1.docker.io", registryHosts[0].Host)
suite.Assert().Equal("/v2", registryHosts[0].Path)
suite.Assert().Nil(registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig) //nolint: errcheck
cfg := &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"http://127.0.0.1:5000/docker.io", "https://some.host"},
},
},
}
registryHosts, err = image.RegistryHosts(cfg)("docker.io")
suite.Require().NoError(err)
suite.Assert().Len(registryHosts, 2)
suite.Assert().Equal("http", registryHosts[0].Scheme)
suite.Assert().Equal("127.0.0.1:5000", registryHosts[0].Host)
suite.Assert().Equal("/docker.io", registryHosts[0].Path)
suite.Assert().Nil(registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig) //nolint: errcheck
suite.Assert().Equal("https", registryHosts[1].Scheme)
suite.Assert().Equal("some.host", registryHosts[1].Host)
suite.Assert().Equal("/v2", registryHosts[1].Path)
suite.Assert().Nil(registryHosts[1].Client.Transport.(*http.Transport).TLSClientConfig) //nolint: errcheck
cfg = &mockConfig{
mirrors: map[string]machine.RegistryMirrorConfig{
"docker.io": {
Endpoints: []string{"https://some.host:123"},
},
},
config: map[string]machine.RegistryConfig{
"some.host:123": {
TLS: &machine.RegistryTLSConfig{
CA: []byte(caCertMock),
// ClientIdentity: &x509.PEMEncodedCertificateAndKey{},
},
Auth: &machine.RegistryAuthConfig{
Username: "root",
Password: "secret",
},
},
},
}
registryHosts, err = image.RegistryHosts(cfg)("docker.io")
suite.Require().NoError(err)
suite.Assert().Len(registryHosts, 1)
suite.Assert().Equal("https", registryHosts[0].Scheme)
suite.Assert().Equal("some.host:123", registryHosts[0].Host)
suite.Assert().Equal("/v2", registryHosts[0].Path)
tlsClientConfig := registryHosts[0].Client.Transport.(*http.Transport).TLSClientConfig //nolint: errcheck
suite.Require().NotNil(tlsClientConfig)
suite.Require().NotNil(tlsClientConfig.RootCAs)
suite.Require().Empty(tlsClientConfig.Certificates)
suite.Require().NotNil(registryHosts[0].Authorizer)
req, err := http.NewRequest("GET", "htts://some.host:123/v2", nil)
suite.Require().NoError(err)
resp := &http.Response{}
resp.Request = req
resp.Header = http.Header{}
resp.Header.Add(http.CanonicalHeaderKey("WWW-Authenticate"), "Basic realm=\"Access to the staging site\", charset=\"UTF-8\"")
suite.Require().NoError(registryHosts[0].Authorizer.AddResponses(context.Background(), []*http.Response{resp}))
suite.Require().NoError(registryHosts[0].Authorizer.Authorize(context.Background(), req))
suite.Assert().Equal("Basic cm9vdDpzZWNyZXQ=", req.Header.Get("Authorization"))
}
func TestResolverSuite(t *testing.T) {
suite.Run(t, new(ResolverSuite))
}
const caCertMock = `-----BEGIN CERTIFICATE-----
MIICjTCCAhSgAwIBAgIIdebfy8FoW6gwCgYIKoZIzj0EAwIwfDELMAkGA1UEBhMC
VVMxDjAMBgNVBAgMBVRleGFzMRAwDgYDVQQHDAdIb3VzdG9uMRgwFgYDVQQKDA9T
U0wgQ29ycG9yYXRpb24xMTAvBgNVBAMMKFNTTC5jb20gUm9vdCBDZXJ0aWZpY2F0
aW9uIEF1dGhvcml0eSBFQ0MwHhcNMTYwMjEyMTgxNDAzWhcNNDEwMjEyMTgxNDAz
WjB8MQswCQYDVQQGEwJVUzEOMAwGA1UECAwFVGV4YXMxEDAOBgNVBAcMB0hvdXN0
b24xGDAWBgNVBAoMD1NTTCBDb3Jwb3JhdGlvbjExMC8GA1UEAwwoU1NMLmNvbSBS
b290IENlcnRpZmljYXRpb24gQXV0aG9yaXR5IEVDQzB2MBAGByqGSM49AgEGBSuB
BAAiA2IABEVuqVDEpiM2nl8ojRfLliJkP9x6jh3MCLOicSS6jkm5BBtHllirLZXI
7Z4INcgn64mMU1jrYor+8FsPazFSY0E7ic3s7LaNGdM0B9y7xgZ/wkWV7Mt/qCPg
CemB+vNH06NjMGEwHQYDVR0OBBYEFILRhXMw5zUE044CkvvlpNHEIejNMA8GA1Ud
EwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUgtGFczDnNQTTjgKS++Wk0cQh6M0wDgYD
VR0PAQH/BAQDAgGGMAoGCCqGSM49BAMCA2cAMGQCMG/n61kRpGDPYbCWe+0F+S8T
kdzt5fxQaxFGRrMcIQBiu77D5+jNB5n5DQtdcj7EqgIwH7y6C+IwJPt8bYBVCpk+
gA0z5Wajs6O7pdWLjwkspl1+4vAHCGht0nxpbl/f5Wpl
-----END CERTIFICATE-----
`

View File

@ -44,7 +44,7 @@ func RunInstallerContainer(r runtime.Runtime, opts ...Option) error {
var img containerd.Image var img containerd.Image
if options.ImagePull { if options.ImagePull {
img, err = image.Pull(ctx, client, r.Config().Machine().Install().Image()) img, err = image.Pull(ctx, r.Config().Machine().Registries(), client, r.Config().Machine().Install().Image())
if err != nil { if err != nil {
return err return err
} }

View File

@ -5,6 +5,8 @@
package machine package machine
import ( import (
"crypto/tls"
stdx509 "crypto/x509"
"fmt" "fmt"
"os" "os"
@ -53,10 +55,11 @@ type Machine interface {
Disks() []Disk Disks() []Disk
Time() Time Time() Time
Env() Env Env() Env
Files() []File Files() ([]File, error)
Type() Type Type() Type
Kubelet() Kubelet Kubelet() Kubelet
Sysctls() map[string]string Sysctls() map[string]string
Registries() Registries
} }
// Env represents a set of environment variables. // Env represents a set of environment variables.
@ -174,3 +177,95 @@ type Kubelet interface {
ExtraArgs() map[string]string ExtraArgs() map[string]string
ExtraMounts() []specs.Mount ExtraMounts() []specs.Mount
} }
// RegistryMirrorConfig represents mirror configuration for a registry.
type RegistryMirrorConfig struct {
// description: |
// List of endpoints (URLs) for registry mirrors to use.
// Endpoint configures HTTP/HTTPS access mode, host name,
// port and path (if path is not set, it defaults to `/v2`).
Endpoints []string `yaml:"endpoints"`
}
// RegistryConfig specifies auth & TLS config per registry.
type RegistryConfig struct {
TLS *RegistryTLSConfig `yaml:"tls,omitempty"`
Auth *RegistryAuthConfig `yaml:"auth,omitempty"`
}
// RegistryAuthConfig specifies authentication configuration for a registry.
type RegistryAuthConfig struct {
// description: |
// Optional registry authentication.
// The meaning of each field is the same with the corresponding field in .docker/config.json.
Username string `yaml:"username"`
// description: |
// Optional registry authentication.
// The meaning of each field is the same with the corresponding field in .docker/config.json.
Password string `yaml:"password"`
// description: |
// Optional registry authentication.
// The meaning of each field is the same with the corresponding field in .docker/config.json.
Auth string `yaml:"auth"`
// description: |
// Optional registry authentication.
// The meaning of each field is the same with the corresponding field in .docker/config.json.
IdentityToken string `yaml:"identityToken"`
}
// RegistryTLSConfig specifies TLS config for HTTPS registries.
type RegistryTLSConfig struct {
// description: |
// Enable mutual TLS authentication with the registry.
// Client certificate and key should be base64-encoded.
// examples:
// - |
// clientIdentity:
// crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJIekNCMHF...
// key: LS0tLS1CRUdJTiBFRDI1NTE5IFBSSVZBVEUgS0VZLS0tLS0KTUM...
ClientIdentity *x509.PEMEncodedCertificateAndKey `yaml:"clientIdentity,omitempty"`
// description: |
// CA registry certificate to add the list of trusted certificates.
// Certificate should be base64-encoded.
CA []byte `yaml:"ca,omitempty"`
// description: |
// Skip TLS server certificate verification (not recommended).
InsecureSkipVerify bool `yaml:"insecureSkipVerify,omitempty"`
}
// GetTLSConfig prepares TLS configuration for connection.
func (cfg *RegistryTLSConfig) GetTLSConfig() (*tls.Config, error) {
tlsConfig := &tls.Config{}
if cfg.ClientIdentity != nil {
cert, err := tls.X509KeyPair(cfg.ClientIdentity.Crt, cfg.ClientIdentity.Key)
if err != nil {
return nil, fmt.Errorf("error parsing client identity: %w", err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
}
if cfg.CA != nil {
tlsConfig.RootCAs = stdx509.NewCertPool()
tlsConfig.RootCAs.AppendCertsFromPEM(cfg.CA)
}
if cfg.InsecureSkipVerify {
tlsConfig.InsecureSkipVerify = true
}
tlsConfig.BuildNameToCertificate()
return tlsConfig, nil
}
// Registries defines the configuration for image fetching.
type Registries interface {
// Mirror config by registry host (first part of image reference).
Mirrors() map[string]RegistryMirrorConfig
// Registry config (auth, TLS) by hostname.
Config() map[string]RegistryConfig
// ExtraFiles generates TOML config for containerd CRI plugin.
ExtraFiles() ([]File, error)
}

View File

@ -25,6 +25,9 @@ func controlPlaneUd(in *Input) (*v1alpha1.Config, error) {
InstallImage: in.InstallImage, InstallImage: in.InstallImage,
InstallBootloader: true, InstallBootloader: true,
}, },
MachineRegistries: v1alpha1.RegistriesConfig{
RegistryMirrors: in.RegistryMirrors,
},
} }
controlPlaneURL, err := url.Parse(in.ControlPlaneEndpoint) controlPlaneURL, err := url.Parse(in.ControlPlaneEndpoint)

View File

@ -83,6 +83,8 @@ type Input struct {
InstallImage string InstallImage string
NetworkConfig *v1alpha1.NetworkConfig NetworkConfig *v1alpha1.NetworkConfig
RegistryMirrors map[string]machine.RegistryMirrorConfig
} }
// GetAPIServerEndpoint returns the formatted host:port of the API server endpoint // GetAPIServerEndpoint returns the formatted host:port of the API server endpoint
@ -320,6 +322,7 @@ func NewInput(clustername string, endpoint string, kubernetesVersion string, opt
InstallDisk: options.InstallDisk, InstallDisk: options.InstallDisk,
InstallImage: options.InstallImage, InstallImage: options.InstallImage,
NetworkConfig: options.NetworkConfig, NetworkConfig: options.NetworkConfig,
RegistryMirrors: options.RegistryMirrors,
} }
return input, nil return input, nil

View File

@ -25,6 +25,9 @@ func initUd(in *Input) (*v1alpha1.Config, error) {
InstallImage: in.InstallImage, InstallImage: in.InstallImage,
InstallBootloader: true, InstallBootloader: true,
}, },
MachineRegistries: v1alpha1.RegistriesConfig{
RegistryMirrors: in.RegistryMirrors,
},
} }
certSANs := in.GetAPIServerSANs() certSANs := in.GetAPIServerSANs()

View File

@ -25,6 +25,9 @@ func workerUd(in *Input) (*v1alpha1.Config, error) {
InstallImage: in.InstallImage, InstallImage: in.InstallImage,
InstallBootloader: true, InstallBootloader: true,
}, },
MachineRegistries: v1alpha1.RegistriesConfig{
RegistryMirrors: in.RegistryMirrors,
},
} }
controlPlaneURL, err := url.Parse(in.ControlPlaneEndpoint) controlPlaneURL, err := url.Parse(in.ControlPlaneEndpoint)

View File

@ -4,7 +4,10 @@
package generate package generate
import v1alpha1 "github.com/talos-systems/talos/pkg/config/types/v1alpha1" import (
"github.com/talos-systems/talos/pkg/config/machine"
v1alpha1 "github.com/talos-systems/talos/pkg/config/types/v1alpha1"
)
// GenOption controls generate options specific to input generation. // GenOption controls generate options specific to input generation.
type GenOption func(o *GenOptions) error type GenOption func(o *GenOptions) error
@ -54,6 +57,19 @@ func WithNetworkConfig(network *v1alpha1.NetworkConfig) GenOption {
} }
} }
// WithRegistryMirror configures registry mirror endpoint(s).
func WithRegistryMirror(host string, endpoints ...string) GenOption {
return func(o *GenOptions) error {
if o.RegistryMirrors == nil {
o.RegistryMirrors = make(map[string]machine.RegistryMirrorConfig)
}
o.RegistryMirrors[host] = machine.RegistryMirrorConfig{Endpoints: endpoints}
return nil
}
}
// GenOptions describes generate parameters. // GenOptions describes generate parameters.
type GenOptions struct { type GenOptions struct {
EndpointList []string EndpointList []string
@ -61,6 +77,7 @@ type GenOptions struct {
InstallImage string InstallImage string
AdditionalSubjectAltNames []string AdditionalSubjectAltNames []string
NetworkConfig *v1alpha1.NetworkConfig NetworkConfig *v1alpha1.NetworkConfig
RegistryMirrors map[string]machine.RegistryMirrorConfig
} }
// DefaultGenOptions returns default options. // DefaultGenOptions returns default options.

View File

@ -14,6 +14,7 @@ import (
"github.com/kubernetes-sigs/bootkube/pkg/asset" "github.com/kubernetes-sigs/bootkube/pkg/asset"
"github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-spec/specs-go"
criplugin "github.com/talos-systems/talos/internal/pkg/containers/cri/containerd"
"github.com/talos-systems/talos/pkg/config/cluster" "github.com/talos-systems/talos/pkg/config/cluster"
"github.com/talos-systems/talos/pkg/config/machine" "github.com/talos-systems/talos/pkg/config/machine"
"github.com/talos-systems/talos/pkg/constants" "github.com/talos-systems/talos/pkg/constants"
@ -103,8 +104,10 @@ func (m *MachineConfig) Env() machine.Env {
} }
// Files implements the Configurator interface. // Files implements the Configurator interface.
func (m *MachineConfig) Files() []machine.File { func (m *MachineConfig) Files() ([]machine.File, error) {
return m.MachineFiles files, err := m.Registries().ExtraFiles()
return append(files, m.MachineFiles...), err
} }
// Type implements the Configurator interface. // Type implements the Configurator interface.
@ -153,6 +156,11 @@ func (m *MachineConfig) SetCertSANs(sans []string) {
m.MachineCertSANs = append(m.MachineCertSANs, sans...) m.MachineCertSANs = append(m.MachineCertSANs, sans...)
} }
// Registries implements the Configurator interface.
func (m *MachineConfig) Registries() machine.Registries {
return &m.MachineRegistries
}
// Image implements the Configurator interface. // Image implements the Configurator interface.
func (k *KubeletConfig) Image() string { func (k *KubeletConfig) Image() string {
image := k.KubeletImage image := k.KubeletImage
@ -302,6 +310,21 @@ func (e *EtcdConfig) ExtraArgs() map[string]string {
return e.EtcdExtraArgs return e.EtcdExtraArgs
} }
// Mirrors implements the Registries interface.
func (r *RegistriesConfig) Mirrors() map[string]machine.RegistryMirrorConfig {
return r.RegistryMirrors
}
// Config implements the Registries interface.
func (r *RegistriesConfig) Config() map[string]machine.RegistryConfig {
return r.RegistryConfig
}
// ExtraFiles implements the Registries interface.
func (r *RegistriesConfig) ExtraFiles() ([]machine.File, error) {
return criplugin.GenerateRegistriesConfig(r)
}
// Token implements the Configurator interface. // Token implements the Configurator interface.
func (c *ClusterConfig) Token() cluster.Token { func (c *ClusterConfig) Token() cluster.Token {
return c return c

View File

@ -179,6 +179,42 @@ type MachineConfig struct {
// kernel.domainname: talos.dev // kernel.domainname: talos.dev
// net.ipv4.ip_forward: "0" // net.ipv4.ip_forward: "0"
MachineSysctls map[string]string `yaml:"sysctls,omitempty"` MachineSysctls map[string]string `yaml:"sysctls,omitempty"`
// description: |
// Used to configure the machine's container image registry mirrors.
//
// Automatically generates matching CRI configuration for registry mirrors.
//
// Section `mirrors` allows to redirect requests for images to non-default registry,
// which might be local registry or caching mirror.
//
// Section `config` provides a way to authenticate to the registry with TLS client
// identity, provide registry CA, or authentication information.
// Authentication information has same meaning with the corresponding field in `.docker/config.json`.
//
// See also matching configuration for [CRI containerd plugin](https://github.com/containerd/cri/blob/master/docs/registry.md).
// examples:
// - |
// registries:
// mirrors:
// docker.io:
// endpoints:
// - https://registry-1.docker.io
// '*':
// endpoints:
// - http://some.host:123/
// config:
// "some.host:123":
// tls:
// CA: ... # base64-encoded CA certificate in PEM format
// clientIdentity:
// cert: ... # base64-encoded client certificate in PEM format
// key: ... # base64-encoded client key in PEM format
// auth:
// username: ...
// password: ...
// auth: ...
// identityToken: ...
MachineRegistries RegistriesConfig `yaml:"registries,omitempty"`
} }
// ClusterConfig reperesents the cluster-wide config values // ClusterConfig reperesents the cluster-wide config values
@ -428,6 +464,26 @@ type TimeConfig struct {
TimeServers []string `yaml:"servers,omitempty"` TimeServers []string `yaml:"servers,omitempty"`
} }
// RegistriesConfig represents the image pull options.
type RegistriesConfig struct {
// description: |
// Specifies mirror configuration for each registry.
// This setting allows to use local pull-through caching registires,
// air-gapped installations, etc.
//
// Registry name is the first segment of image identifier, with 'docker.io'
// being default one.
// Name '*' catches any registry names not specified explicitly.
RegistryMirrors map[string]machine.RegistryMirrorConfig `yaml:"mirrors,omitempty"`
// description: |
// Specifies TLS & auth configuration for HTTPS image registries.
// Mutual TLS can be enabled with 'clientIdentity' option.
//
// TLS configuration can be skipped if registry has trusted
// server certificate.
RegistryConfig map[string]machine.RegistryConfig `yaml:"config,omitempty"`
}
// PodCheckpointer represents the pod-checkpointer config values // PodCheckpointer represents the pod-checkpointer config values
type PodCheckpointer struct { type PodCheckpointer struct {
// description: | // description: |