mirror of
https://github.com/siderolabs/talos.git
synced 2025-08-23 07:31:13 +02:00
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:
parent
33332f4c74
commit
e1779ac77c
@ -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(®istryMirrors, "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)")
|
||||||
|
@ -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(®istryMirrors, "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"))
|
||||||
|
@ -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)
|
||||||
|
@ -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")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@ -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
1
go.mod
@ -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
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
153
internal/pkg/containers/cri/containerd/config.go
Normal file
153
internal/pkg/containers/cri/containerd/config.go
Normal 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
|
||||||
|
}
|
117
internal/pkg/containers/cri/containerd/config_test.go
Normal file
117
internal/pkg/containers/cri/containerd/config_test.go
Normal 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))
|
||||||
|
}
|
6
internal/pkg/containers/cri/containerd/containerd.go
Normal file
6
internal/pkg/containers/cri/containerd/containerd.go
Normal 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
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
167
internal/pkg/containers/image/resolver.go
Normal file
167
internal/pkg/containers/image/resolver.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
225
internal/pkg/containers/image/resolver_test.go
Normal file
225
internal/pkg/containers/image/resolver_test.go
Normal 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-----
|
||||||
|
`
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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: |
|
||||||
|
Loading…
x
Reference in New Issue
Block a user