diff --git a/.semaphore/semaphore.yml b/.semaphore/semaphore.yml index afc94b6b7..0093131b0 100644 --- a/.semaphore/semaphore.yml +++ b/.semaphore/semaphore.yml @@ -11,7 +11,7 @@ blocks: task: env_vars: - name: GIMME_GO_VERSION - value: "1.14.2" + value: "1.16.3" jobs: - name: Tests commands: diff --git a/cmd/ignite/cmd/cmdutil/flags.go b/cmd/ignite/cmd/cmdutil/flags.go index d31712cc8..013f99ec7 100644 --- a/cmd/ignite/cmd/cmdutil/flags.go +++ b/cmd/ignite/cmd/cmdutil/flags.go @@ -35,3 +35,7 @@ func AddSSHFlags(fs *pflag.FlagSet, identityFile *string, timeout *uint32) { fs.StringVarP(identityFile, "identity", "i", "", "Override the vm's default identity file") fs.Uint32Var(timeout, "timeout", constants.SSH_DEFAULT_TIMEOUT_SECONDS, "Timeout waiting for connection in seconds") } + +func AddRegistryConfigDirFlag(fs *pflag.FlagSet, dir *string) { + fs.StringVar(dir, "registry-config-dir", "", "Directory containing the registry configuration (default ~/.docker/)") +} diff --git a/cmd/ignite/cmd/cmdutil/providers.go b/cmd/ignite/cmd/cmdutil/providers.go new file mode 100644 index 000000000..36ecdaa03 --- /dev/null +++ b/cmd/ignite/cmd/cmdutil/providers.go @@ -0,0 +1,22 @@ +package cmdutil + +import ( + log "github.com/sirupsen/logrus" + + "github.com/weaveworks/ignite/pkg/providers" +) + +// ResolveRegistryConfigDir reads various configuration to resolve the registry +// configuration directory. +func ResolveRegistryConfigDir() { + if providers.ComponentConfig != nil { + // Set the providers registry config dir from ignite configuration if + // it's empty. When it's set in the providers and in the ignite + // configuration, log about the override. + if providers.RegistryConfigDir == "" { + providers.RegistryConfigDir = providers.ComponentConfig.Spec.RegistryConfigDir + } else if providers.ComponentConfig.Spec.RegistryConfigDir != "" { + log.Debug("registry-config-dir flag overriding the ignite configuration") + } + } +} diff --git a/cmd/ignite/cmd/imgcmd/import.go b/cmd/ignite/cmd/imgcmd/import.go index 66edbcf42..d1f0f6c45 100644 --- a/cmd/ignite/cmd/imgcmd/import.go +++ b/cmd/ignite/cmd/imgcmd/import.go @@ -38,4 +38,5 @@ func NewCmdImport(out io.Writer) *cobra.Command { func addImportFlags(fs *pflag.FlagSet) { runtimeflag.RuntimeVar(fs, &providers.RuntimeName) + cmdutil.AddRegistryConfigDirFlag(fs, &providers.RegistryConfigDir) } diff --git a/cmd/ignite/cmd/kerncmd/import.go b/cmd/ignite/cmd/kerncmd/import.go index 2ad3af111..19e41b899 100644 --- a/cmd/ignite/cmd/kerncmd/import.go +++ b/cmd/ignite/cmd/kerncmd/import.go @@ -38,4 +38,5 @@ func NewCmdImport(out io.Writer) *cobra.Command { func addImportFlags(fs *pflag.FlagSet) { runtimeflag.RuntimeVar(fs, &providers.RuntimeName) + cmdutil.AddRegistryConfigDirFlag(fs, &providers.RegistryConfigDir) } diff --git a/cmd/ignite/cmd/vmcmd/create.go b/cmd/ignite/cmd/vmcmd/create.go index 18146424f..3650ec46d 100644 --- a/cmd/ignite/cmd/vmcmd/create.go +++ b/cmd/ignite/cmd/vmcmd/create.go @@ -88,4 +88,5 @@ func addCreateFlags(fs *pflag.FlagSet, cf *run.CreateFlags) { runtimeflag.RuntimeVar(fs, &providers.RuntimeName) networkflag.NetworkPluginVar(fs, &providers.NetworkPluginName) cmdutil.AddIDPrefixFlag(fs, &providers.IDPrefix) + cmdutil.AddRegistryConfigDirFlag(fs, &providers.RegistryConfigDir) } diff --git a/cmd/ignite/run/create.go b/cmd/ignite/run/create.go index 9e450ed99..6c8df71b1 100644 --- a/cmd/ignite/run/create.go +++ b/cmd/ignite/run/create.go @@ -6,6 +6,7 @@ import ( "path" "strings" + "github.com/weaveworks/ignite/cmd/ignite/cmd/cmdutil" api "github.com/weaveworks/ignite/pkg/apis/ignite" "github.com/weaveworks/ignite/pkg/apis/ignite/scheme" "github.com/weaveworks/ignite/pkg/apis/ignite/validation" @@ -58,6 +59,9 @@ func (cf *CreateFlags) NewCreateOptions(args []string, fs *flag.FlagSet) (*Creat baseVM.Spec = providers.ComponentConfig.Spec.VMDefaults } + // Resolve registry configuration used for pulling image if required. + cmdutil.ResolveRegistryConfigDir() + // Initialize the VM's Prefixer baseVM.Status.IDPrefix = providers.IDPrefix // Set the runtime and network-plugin on the VM, then override the global config. diff --git a/cmd/ignite/run/import.go b/cmd/ignite/run/import.go index b9e415606..2a5ecdb2f 100644 --- a/cmd/ignite/run/import.go +++ b/cmd/ignite/run/import.go @@ -1,6 +1,7 @@ package run import ( + "github.com/weaveworks/ignite/cmd/ignite/cmd/cmdutil" api "github.com/weaveworks/ignite/pkg/apis/ignite" meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" "github.com/weaveworks/ignite/pkg/config" @@ -16,6 +17,8 @@ func ImportImage(source string) (image *api.Image, err error) { return nil, err } + cmdutil.ResolveRegistryConfigDir() + ociRef, err := meta.NewOCIImageRef(source) if err != nil { return @@ -38,6 +41,8 @@ func ImportKernel(source string) (kernel *api.Kernel, err error) { return nil, err } + cmdutil.ResolveRegistryConfigDir() + ociRef, err := meta.NewOCIImageRef(source) if err != nil { return diff --git a/docs/api/ignite_v1alpha3.md b/docs/api/ignite_v1alpha3.md index 313351b03..d54ed22d4 100644 --- a/docs/api/ignite_v1alpha3.md +++ b/docs/api/ignite_v1alpha3.md @@ -15,6 +15,11 @@ - [Constants](#pkg-constants) - [Variables](#pkg-variables) + - [func + Convert\_ignite\_ConfigurationSpec\_To\_v1alpha3\_ConfigurationSpec(in + *ignite.ConfigurationSpec, out *ConfigurationSpec, s + conversion.Scope) + error](#Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec) - [func SetDefaults\_ConfigurationSpec(obj \*ConfigurationSpec)](#SetDefaults_ConfigurationSpec) - [func SetDefaults\_PoolSpec(obj \*PoolSpec)](#SetDefaults_PoolSpec) @@ -60,6 +65,7 @@ #### Package files +[conversion.go](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha3/conversion.go) [defaults.go](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha3/defaults.go) [doc.go](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha3/doc.go) [json.go](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha3/json.go) @@ -106,6 +112,16 @@ var SchemeGroupVersion = schema.GroupVersion{ SchemeGroupVersion is group version used to register these objects +## func [Convert\_ignite\_ConfigurationSpec\_To\_v1alpha3\_ConfigurationSpec](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha3/conversion.go?s=261:408#L9) + +``` go +func Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(in *ignite.ConfigurationSpec, out *ConfigurationSpec, s conversion.Scope) error +``` + +Convert\_ignite\_ConfigurationSpec\_To\_v1alpha3\_ConfigurationSpec +calls the autogenerated conversion function along with custom conversion +logic + ## func [SetDefaults\_ConfigurationSpec](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha3/defaults.go?s=1785:1843#L71) ``` go diff --git a/docs/api/ignite_v1alpha4.md b/docs/api/ignite_v1alpha4.md index 936c35d5f..efb64d116 100644 --- a/docs/api/ignite_v1alpha4.md +++ b/docs/api/ignite_v1alpha4.md @@ -166,14 +166,15 @@ type Configuration struct { Configuration represents the ignite runtime configuration. +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object -## type [ConfigurationSpec](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha4/types.go?s=9475:9786#L261) +## type [ConfigurationSpec](https://github.com/weaveworks/ignite/tree/main/pkg/apis/ignite/v1alpha4/types.go?s=9475:9883#L261) ``` go type ConfigurationSpec struct { - Runtime igniteRuntime.Name `json:"runtime,omitempty"` - NetworkPlugin igniteNetwork.PluginName `json:"networkPlugin,omitempty"` - VMDefaults VMSpec `json:"vmDefaults,omitempty"` - IDPrefix string `json:"idPrefix,omitempty"` + Runtime igniteRuntime.Name `json:"runtime,omitempty"` + NetworkPlugin igniteNetwork.PluginName `json:"networkPlugin,omitempty"` + VMDefaults VMSpec `json:"vmDefaults,omitempty"` + IDPrefix string `json:"idPrefix,omitempty"` + RegistryConfigDir string `json:"registryConfigDir,omitempty"` } ``` diff --git a/docs/cli/ignite/ignite_create.md b/docs/cli/ignite/ignite_create.md index 9e5f75d2a..5a2f9245d 100644 --- a/docs/cli/ignite/ignite_create.md +++ b/docs/cli/ignite/ignite_create.md @@ -34,24 +34,25 @@ ignite create [flags] ### Options ``` - --config string Specify a path to a file with the API resources you want to pass - -f, --copy-files strings Copy files/directories from the host to the created VM - --cpus uint VM vCPU count, 1 or even numbers between 1 and 32 (default 1) - -h, --help help for create - --id-prefix string Prefix string for system identifiers (default ignite) - --kernel-args string Set the command line for the kernel (default "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp") - -k, --kernel-image oci-image Specify an OCI image containing the kernel at /boot/vmlinux and optionally, modules (default weaveworks/ignite-kernel:5.4.108) - -l, --label stringArray Set a label (foo=bar) - --memory size Amount of RAM to allocate for the VM (default 512.0 MB) - -n, --name string Specify the name - --network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni) - -p, --ports strings Map host ports to VM ports - --require-name Require VM name to be passed, no name generation - --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) - --sandbox-image oci-image Specify an OCI image for the VM sandbox (default weaveworks/ignite:dev) - -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) - --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) - -v, --volumes volume Expose block devices from the host inside the VM + --config string Specify a path to a file with the API resources you want to pass + -f, --copy-files strings Copy files/directories from the host to the created VM + --cpus uint VM vCPU count, 1 or even numbers between 1 and 32 (default 1) + -h, --help help for create + --id-prefix string Prefix string for system identifiers (default ignite) + --kernel-args string Set the command line for the kernel (default "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp") + -k, --kernel-image oci-image Specify an OCI image containing the kernel at /boot/vmlinux and optionally, modules (default weaveworks/ignite-kernel:5.4.108) + -l, --label stringArray Set a label (foo=bar) + --memory size Amount of RAM to allocate for the VM (default 512.0 MB) + -n, --name string Specify the name + --network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni) + -p, --ports strings Map host ports to VM ports + --registry-config-dir string Directory containing the registry configuration (default ~/.docker/) + --require-name Require VM name to be passed, no name generation + --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) + --sandbox-image oci-image Specify an OCI image for the VM sandbox (default weaveworks/ignite:dev) + -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) + --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) + -v, --volumes volume Expose block devices from the host inside the VM ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_image_import.md b/docs/cli/ignite/ignite_image_import.md index 749ba4384..bda74abad 100644 --- a/docs/cli/ignite/ignite_image_import.md +++ b/docs/cli/ignite/ignite_image_import.md @@ -17,8 +17,9 @@ ignite image import [flags] ### Options ``` - -h, --help help for import - --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) + -h, --help help for import + --registry-config-dir string Directory containing the registry configuration (default ~/.docker/) + --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_kernel_import.md b/docs/cli/ignite/ignite_kernel_import.md index 74b879cd4..5988fe9bd 100644 --- a/docs/cli/ignite/ignite_kernel_import.md +++ b/docs/cli/ignite/ignite_kernel_import.md @@ -17,8 +17,9 @@ ignite kernel import [flags] ### Options ``` - -h, --help help for import - --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) + -h, --help help for import + --registry-config-dir string Directory containing the registry configuration (default ~/.docker/) + --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_run.md b/docs/cli/ignite/ignite_run.md index fd3f8edff..5ebd382d8 100644 --- a/docs/cli/ignite/ignite_run.md +++ b/docs/cli/ignite/ignite_run.md @@ -42,6 +42,7 @@ ignite run [flags] -n, --name string Specify the name --network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni) -p, --ports strings Map host ports to VM ports + --registry-config-dir string Directory containing the registry configuration (default ~/.docker/) --require-name Require VM name to be passed, no name generation --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) --sandbox-image oci-image Specify an OCI image for the VM sandbox (default weaveworks/ignite:dev) diff --git a/docs/cli/ignite/ignite_vm_create.md b/docs/cli/ignite/ignite_vm_create.md index ff1359d77..def7403d3 100644 --- a/docs/cli/ignite/ignite_vm_create.md +++ b/docs/cli/ignite/ignite_vm_create.md @@ -34,24 +34,25 @@ ignite vm create [flags] ### Options ``` - --config string Specify a path to a file with the API resources you want to pass - -f, --copy-files strings Copy files/directories from the host to the created VM - --cpus uint VM vCPU count, 1 or even numbers between 1 and 32 (default 1) - -h, --help help for create - --id-prefix string Prefix string for system identifiers (default ignite) - --kernel-args string Set the command line for the kernel (default "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp") - -k, --kernel-image oci-image Specify an OCI image containing the kernel at /boot/vmlinux and optionally, modules (default weaveworks/ignite-kernel:5.4.108) - -l, --label stringArray Set a label (foo=bar) - --memory size Amount of RAM to allocate for the VM (default 512.0 MB) - -n, --name string Specify the name - --network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni) - -p, --ports strings Map host ports to VM ports - --require-name Require VM name to be passed, no name generation - --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) - --sandbox-image oci-image Specify an OCI image for the VM sandbox (default weaveworks/ignite:dev) - -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) - --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) - -v, --volumes volume Expose block devices from the host inside the VM + --config string Specify a path to a file with the API resources you want to pass + -f, --copy-files strings Copy files/directories from the host to the created VM + --cpus uint VM vCPU count, 1 or even numbers between 1 and 32 (default 1) + -h, --help help for create + --id-prefix string Prefix string for system identifiers (default ignite) + --kernel-args string Set the command line for the kernel (default "console=ttyS0 reboot=k panic=1 pci=off ip=dhcp") + -k, --kernel-image oci-image Specify an OCI image containing the kernel at /boot/vmlinux and optionally, modules (default weaveworks/ignite-kernel:5.4.108) + -l, --label stringArray Set a label (foo=bar) + --memory size Amount of RAM to allocate for the VM (default 512.0 MB) + -n, --name string Specify the name + --network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni) + -p, --ports strings Map host ports to VM ports + --registry-config-dir string Directory containing the registry configuration (default ~/.docker/) + --require-name Require VM name to be passed, no name generation + --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) + --sandbox-image oci-image Specify an OCI image for the VM sandbox (default weaveworks/ignite:dev) + -s, --size size VM filesystem size, for example 5GB or 2048MB (default 4.0 GB) + --ssh[=] Enable SSH for the VM. If is given, it will be imported as the public key. If just '--ssh' is specified, a new keypair will be generated. (default is unset, which disables SSH access to the VM) + -v, --volumes volume Expose block devices from the host inside the VM ``` ### Options inherited from parent commands diff --git a/docs/cli/ignite/ignite_vm_run.md b/docs/cli/ignite/ignite_vm_run.md index a9bdc0d7e..375721113 100644 --- a/docs/cli/ignite/ignite_vm_run.md +++ b/docs/cli/ignite/ignite_vm_run.md @@ -42,6 +42,7 @@ ignite vm run [flags] -n, --name string Specify the name --network-plugin plugin Network plugin to use. Available options are: [cni docker-bridge] (default cni) -p, --ports strings Map host ports to VM ports + --registry-config-dir string Directory containing the registry configuration (default ~/.docker/) --require-name Require VM name to be passed, no name generation --runtime runtime Container runtime to use. Available options are: [docker containerd] (default containerd) --sandbox-image oci-image Specify an OCI image for the VM sandbox (default weaveworks/ignite:dev) diff --git a/docs/usage.md b/docs/usage.md index 93b8049a2..5036c7cfb 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -64,14 +64,17 @@ Now the `weaveworks/ignite-ubuntu` image is imported and ready for VM use. ### Configuring image registries -Ignite's runtime configuration for image registry uses the docker client -configuration. To add a new registry to docker client configuration, run +Ignite's runtime configuration for image registry uses the docker registry +configuration. To add a new registry to docker registry configuration, run `docker login `. This will create `$HOME/.docker/config.json` in the user's home directory. When ignite runs, it'll check the user's home -directory for docker client configuration file, load the registry configuration +directory for docker registry configuration file, load the registry configuration if found and use it. -An example of a docker client configuration file: +!!! Note + On many systems, running `sudo ignite` will set the `$HOME` directory to `/root`. + +An example of a docker registry configuration file: ```json @@ -80,6 +83,9 @@ An example of a docker client configuration file: "https://index.docker.io/v1/": { "auth": "" }, + "http://localhost:5000": { + "auth": "" + }, "gcr.io": { "auth": "" } @@ -92,12 +98,24 @@ the token is a base64 encoded value of `:`. For `gcr.io`, it's a [json key][json-key] file. Using docker [credential helpers][credential-helpers] also works but please ensure that the required credential helper program is installed to handle the credentials. If -the docker client configuration contains `"credHelpers"` block, but the +the docker registry configuration contains `"credHelpers"` block, but the associated helper program isn't installed or not configured properly, ignite image pull will fail with errors related to the specific credential helper. In presence of both auth tokens and credential helpers in a configuration file, credential helper takes precedence. +The `--registry-config-dir` flag can be used to override the default directory(`$HOME/.docker/`). +This can also be done from the ignite [Configuration](./ignite-configuration). + +When using the `containerd` runtime to pull images, TLS verification can be disabled, +and `http://` protocols can be specified by using the client-side `IGNITE_CONTAINERD_INSECURE_REGISTRIES` +environment variable as a comma separate list. +In this list, the protocol is completely ignored, because it's specified by the registry-configuration: + +```shell +IGNITE_CONTAINERD_INSECURE_REGISTRIES="localhost:5000,localhost:5001,example.com,http://example.com" +``` + [json-key]: https://cloud.google.com/container-registry/docs/advanced-authentication#json-key [credential-helpers]: https://docs.docker.com/engine/reference/commandline/login/#credential-helpers diff --git a/e2e/registry_auth_test.go b/e2e/registry_auth_test.go new file mode 100644 index 000000000..3d00e6043 --- /dev/null +++ b/e2e/registry_auth_test.go @@ -0,0 +1,234 @@ +package e2e + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "testing" + + "gotest.tools/assert" + + "github.com/weaveworks/ignite/e2e/util" + "github.com/weaveworks/ignite/pkg/runtime" + "github.com/weaveworks/ignite/pkg/runtime/containerd" +) + +const ( + httpTestOSImage = "127.5.0.1:5080/weaveworks/ignite-ubuntu:test" + httpTestKernelImage = "127.5.0.1:5080/weaveworks/ignite-kernel:test" + httpsTestOSImage = "127.5.0.1:5443/weaveworks/ignite-ubuntu:test" + httpsTestKernelImage = "127.5.0.1:5443/weaveworks/ignite-kernel:test" +) + +// registry config with auth info for the registry setup in +// e2e/util/setup-private-registry.sh. +// NOTE: Update the auth token if the credentials in setup-private-registry.sh +// is updated. +const registryConfigContent = ` +{ + "auths": { + "http://127.5.0.1:5080": { + "auth": "aHR0cF90ZXN0dXNlcjpodHRwX3Rlc3RwYXNzd29yZA==" + }, + "https://127.5.0.1:5443": { + "auth": "aHR0cHNfdGVzdHVzZXI6aHR0cHNfdGVzdHBhc3N3b3Jk" + } + } +} +` + +func TestPullFromAuthRegistry(t *testing.T) { + assert.Assert(t, e2eHome != "", "IGNITE_E2E_HOME should be set") + + // Set up the registries. + startRegistries := exec.Command( + "bash", "util/setup-private-registry.sh", + ) + startOutput, startErr := startRegistries.CombinedOutput() + if startErr != nil { + t.Fatalf("failed to set up registries: %v, %s", startErr, string(startOutput)) + } + + // Stop the registries at the end. + stopRegistries := exec.Command( + "docker", "stop", + "ignite-test-http-registry", "ignite-test-https-registry", + ) + defer func() { + if stopOutput, stopErr := stopRegistries.CombinedOutput(); stopErr != nil { + t.Fatalf("failed to stop registries: %v, %s", stopErr, string(stopOutput)) + } + }() + + os.Setenv(containerd.InsecureRegistriesEnvVar, "http://127.5.0.1:5080,https://127.5.0.1:5443") + defer os.Unsetenv(containerd.InsecureRegistriesEnvVar) + + // Create a registry config directory to use in test. + emptyDir, err := ioutil.TempDir("", "ignite-test") + assert.NilError(t, err) + defer os.RemoveAll(emptyDir) + + // Create a registry config directory to use in test. + rcDir, err := ioutil.TempDir("", "ignite-test") + assert.NilError(t, err) + defer os.RemoveAll(rcDir) + + // Ensure the directory exists and create a config file in the + // directory. + assert.NilError(t, os.MkdirAll(rcDir, 0755)) + configPath := filepath.Join(rcDir, "config.json") + assert.NilError(t, os.WriteFile(configPath, []byte(registryConfigContent), 0600)) + defer os.Remove(configPath) + + templateConfig := `--- +apiVersion: ignite.weave.works/v1alpha4 +kind: Configuration +metadata: + name: test-config +spec: + registryConfigDir: %s +` + igniteConfigContent := fmt.Sprintf(templateConfig, rcDir) + + type testCase struct { + name string + runtime runtime.Name + registryConfigFlag string + igniteConfig string + wantErr bool + } + cases := []testCase{ + { + name: "no auth info - containerd", + runtime: runtime.RuntimeContainerd, + wantErr: true, + }, + { + name: "no auth info - docker", + runtime: runtime.RuntimeDocker, + wantErr: true, + }, + { + name: "registry config flag - containerd", + runtime: runtime.RuntimeContainerd, + registryConfigFlag: rcDir, + }, + { + name: "registry config flag - docker", + runtime: runtime.RuntimeDocker, + registryConfigFlag: rcDir, + }, + { + name: "registry config in ignite config - containerd", + runtime: runtime.RuntimeContainerd, + igniteConfig: igniteConfigContent, + }, + { + name: "registry config in ignite config - docker", + runtime: runtime.RuntimeDocker, + igniteConfig: igniteConfigContent, + }, + // Following sets the registry config dir to a location without a valid + // registry config file, although the registry config dir in the ignite + // config is correct, the import fails due to bad configuration by the + // flag override. + { + name: "flag override registry config - containerd", + runtime: runtime.RuntimeContainerd, + registryConfigFlag: emptyDir, + igniteConfig: igniteConfigContent, + wantErr: true, + }, + { + name: "flag override registry config - docker", + runtime: runtime.RuntimeDocker, + registryConfigFlag: emptyDir, + igniteConfig: igniteConfigContent, + wantErr: true, + }, + // Following sets the registry config dir via flag without any actual + // registry config. Import fails due to missing auth info in the given + // registry config dir. + { + name: "invalid registry config - containerd", + runtime: runtime.RuntimeContainerd, + registryConfigFlag: emptyDir, + wantErr: true, + }, + { + name: "invalid registry config - docker", + runtime: runtime.RuntimeDocker, + registryConfigFlag: emptyDir, + wantErr: true, + }, + } + + testFunc := func(rt testCase, osImage, kernelImage string) func(t *testing.T) { + return func(t *testing.T) { + igniteCmd := util.NewCommand(t, igniteBin) + + // Remove images from ignite store and runtime store. Remove + // individually because an error in deleting one image cancels the + // whole command. + // TODO: Improve image rm to not fail completely when there are + // multiple images and some are not found. + util.RmiCompletely(osImage, igniteCmd, rt.runtime) + util.RmiCompletely(kernelImage, igniteCmd, rt.runtime) + + // Write ignite config if provided. + var igniteConfigPath string + if len(rt.igniteConfig) > 0 { + igniteFile, err := ioutil.TempFile("", "ignite-config-file-test") + if err != nil { + t.Fatalf("failed to create a file: %v", err) + } + igniteConfigPath = igniteFile.Name() + + _, err = igniteFile.WriteString(rt.igniteConfig) + assert.NilError(t, err) + assert.NilError(t, igniteFile.Close()) + + defer os.Remove(igniteFile.Name()) + } + + // Construct the ignite image import command. + imageImportCmdArgs := []string{"--runtime", rt.runtime.String()} + if len(rt.registryConfigFlag) > 0 { + imageImportCmdArgs = append(imageImportCmdArgs, "--registry-config-dir", rt.registryConfigFlag) + } + if len(igniteConfigPath) > 0 { + imageImportCmdArgs = append(imageImportCmdArgs, "--ignite-config", igniteConfigPath) + } + + // Run image import. + _, importErr := igniteCmd.New(). + With("image", "import", osImage). + With(imageImportCmdArgs...). + Cmd.CombinedOutput() + if (importErr != nil) != rt.wantErr { + t.Errorf("expected error %t, actual: %v", rt.wantErr, importErr) + } + + // Run kernel import. + _, importErr = igniteCmd.New(). + With("image", "import", kernelImage). + With(imageImportCmdArgs...). + Cmd.CombinedOutput() + if (importErr != nil) != rt.wantErr { + t.Errorf("expected error %t, actual: %v", rt.wantErr, importErr) + } + } + } + + for _, rt := range cases { + rt := rt + t.Run("http_"+rt.name, testFunc(rt, httpTestOSImage, httpTestKernelImage)) + } + + for _, rt := range cases { + rt := rt + t.Run("https_"+rt.name, testFunc(rt, httpsTestOSImage, httpsTestKernelImage)) + } +} diff --git a/e2e/util/image.go b/e2e/util/image.go new file mode 100644 index 000000000..e48f409a3 --- /dev/null +++ b/e2e/util/image.go @@ -0,0 +1,40 @@ +package util + +import ( + "os/exec" + + "github.com/weaveworks/ignite/pkg/runtime" +) + +// RmiDocker removes an image from docker content store. +func RmiDocker(img string) { + _, _ = exec.Command( + "docker", + "rmi", img, + ).CombinedOutput() +} + +// RmiContainerd removes an image from containerd content store. +func RmiContainerd(img string) { + _, _ = exec.Command( + "ctr", "-n", "firecracker", + "image", "rm", img, + ).CombinedOutput() +} + +// rmiCompletely removes a given image completely, from ignite image store and +// runtime image store. +func RmiCompletely(img string, cmd *Command, rt runtime.Name) { + // Remote from ignite content store. + _, _ = cmd.New(). + With("image", "rm", img). + Cmd.CombinedOutput() + + // Remove from runtime content store. + switch rt { + case runtime.RuntimeContainerd: + RmiContainerd(img) + case runtime.RuntimeDocker: + RmiDocker(img) + } +} diff --git a/e2e/util/setup-private-registry.sh b/e2e/util/setup-private-registry.sh new file mode 100644 index 000000000..0f83f64d9 --- /dev/null +++ b/e2e/util/setup-private-registry.sh @@ -0,0 +1,102 @@ +#!/usr/bin/env bash + +set -eu + +# This script runs two local, private, docker registries, +# one with with self-signed certificates, TLS, and HTTP, +# the other with plain HTTP, both using basic auth. + +HTTP_REGISTRY_SECRET_PATH="$(mktemp -d)/ignite-test-registry-http" +HTTPS_REGISTRY_SECRET_PATH="$(mktemp -d)/ignite-test-registry-https" + +PRIVATE_KEY="${HTTPS_REGISTRY_SECRET_PATH}/certs/domain.key" +CERT="${HTTPS_REGISTRY_SECRET_PATH}/certs/domain.crt" + +HTTP_USERNAME="http_testuser" +HTTP_PASSWORD="http_testpassword" +HTTPS_USERNAME="https_testuser" +HTTPS_PASSWORD="https_testpassword" + +BIND_IP="127.5.0.1" +HTTP_ADDR="${BIND_IP}:5080" +HTTPS_ADDR="${BIND_IP}:5443" + +OS_IMG="weaveworks/ignite-ubuntu:latest" +KERNEL_IMG="weaveworks/ignite-kernel:5.4.108" +HTTP_LOCAL_OS_IMG="${HTTP_ADDR}/weaveworks/ignite-ubuntu:test" +HTTP_LOCAL_KERNEL_IMG="${HTTP_ADDR}/weaveworks/ignite-kernel:test" +HTTPS_LOCAL_OS_IMG="${HTTPS_ADDR}/weaveworks/ignite-ubuntu:test" +HTTPS_LOCAL_KERNEL_IMG="${HTTPS_ADDR}/weaveworks/ignite-kernel:test" + +# Clear any existing registry secret and create new directories. +mkdir -p "${HTTP_REGISTRY_SECRET_PATH}/auth" +mkdir -p "${HTTPS_REGISTRY_SECRET_PATH}/"{certs,auth} + +# Generate key and cert. +openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 \ + -subj "/C=US/ST=Foo/L=Bar/O=Weave" \ + -keyout "${PRIVATE_KEY}" -out "${CERT}" +chmod 400 "${PRIVATE_KEY}" + +# Use a test config directory to avoid modifying the user's default docker +# configuration. +DOCKER_CONFIG_DIR="$(mktemp -d)/ignite-docker-config" +mkdir -p "${DOCKER_CONFIG_DIR}" + +DOCKER="docker --config=${DOCKER_CONFIG_DIR}" + +# Create htpasswd files. +${DOCKER} run --rm \ + --entrypoint htpasswd \ + httpd:2 -Bbn "${HTTP_USERNAME}" "${HTTP_PASSWORD}" > "${HTTP_REGISTRY_SECRET_PATH}/auth/htpasswd" + +${DOCKER} run --rm \ + --entrypoint htpasswd \ + httpd:2 -Bbn "${HTTPS_USERNAME}" "${HTTPS_PASSWORD}" > "${HTTPS_REGISTRY_SECRET_PATH}/auth/htpasswd" + +# Run the registries +${DOCKER} run -d --rm \ + --name ignite-test-http-registry \ + -v "${HTTP_REGISTRY_SECRET_PATH}/auth":/auth \ + -e REGISTRY_AUTH=htpasswd \ + -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ + -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ + -p "${HTTP_ADDR}":5000 \ + registry:2 + +${DOCKER} run -d --rm \ + --name ignite-test-https-registry \ + -v "${HTTPS_REGISTRY_SECRET_PATH}/auth":/auth \ + -v "${HTTPS_REGISTRY_SECRET_PATH}/certs":/certs \ + -e REGISTRY_AUTH=htpasswd \ + -e REGISTRY_AUTH_HTPASSWD_REALM="Registry Realm" \ + -e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \ + -e REGISTRY_HTTP_TLS_CERTIFICATE=/certs/domain.crt \ + -e REGISTRY_HTTP_TLS_KEY=/certs/domain.key \ + -p "${HTTPS_ADDR}":5000 \ + registry:2 + +# Login, push test images to download in tests and logout. +${DOCKER} pull "${OS_IMG}" +${DOCKER} pull "${KERNEL_IMG}" + +${DOCKER} tag "${OS_IMG}" "${HTTP_LOCAL_OS_IMG}" +${DOCKER} tag "${KERNEL_IMG}" "${HTTP_LOCAL_KERNEL_IMG}" +${DOCKER} tag "${OS_IMG}" "${HTTPS_LOCAL_OS_IMG}" +${DOCKER} tag "${KERNEL_IMG}" "${HTTPS_LOCAL_KERNEL_IMG}" + +${DOCKER} login -u "${HTTP_USERNAME}" -p "${HTTP_PASSWORD}" "https://${HTTP_ADDR}" +${DOCKER} login -u "${HTTPS_USERNAME}" -p "${HTTPS_PASSWORD}" "https://${HTTPS_ADDR}" + +# push in parallel, block until all finished +${DOCKER} push "${HTTP_LOCAL_OS_IMG}" & +${DOCKER} push "${HTTP_LOCAL_KERNEL_IMG}" & +${DOCKER} push "${HTTPS_LOCAL_OS_IMG}" & +${DOCKER} push "${HTTPS_LOCAL_KERNEL_IMG}" & +wait + +${DOCKER} logout "http://${HTTP_ADDR}" +${DOCKER} logout "https://${HTTPS_ADDR}" + +${DOCKER} rmi "${HTTP_LOCAL_OS_IMG}" "${HTTP_LOCAL_KERNEL_IMG}" +${DOCKER} rmi "${HTTPS_LOCAL_OS_IMG}" "${HTTPS_LOCAL_KERNEL_IMG}" diff --git a/go.mod b/go.mod index 7a26b6f2f..b1e12834e 100644 --- a/go.mod +++ b/go.mod @@ -54,7 +54,7 @@ require ( golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 golang.org/x/net v0.0.0-20210415231046-e915ea6b2b7d // indirect golang.org/x/sys v0.0.0-20210510120138-977fb7262007 - golang.org/x/tools v0.1.1 // indirect + golang.org/x/tools v0.1.2 // indirect google.golang.org/genproto v0.0.0-20210416161957-9910b6c460de // indirect google.golang.org/grpc v1.37.0 // indirect gotest.tools v2.2.0+incompatible diff --git a/go.sum b/go.sum index 3fe693f08..0946b6129 100644 --- a/go.sum +++ b/go.sum @@ -1285,8 +1285,8 @@ golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roY golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1 h1:wGiQel/hW0NnEkJUk8lbzkX2gFJU6PFxf1v5OlCfuOs= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.2 h1:kRBLX7v7Af8W7Gdbbc908OJcdgtK8bOz9Uaj8/F1ACA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/apis/ignite/types.go b/pkg/apis/ignite/types.go index d595ce14b..b86a400b5 100644 --- a/pkg/apis/ignite/types.go +++ b/pkg/apis/ignite/types.go @@ -259,8 +259,9 @@ type Configuration struct { // ConfigurationSpec defines the ignite configuration. type ConfigurationSpec struct { - Runtime igniteRuntime.Name `json:"runtime,omitempty"` - NetworkPlugin igniteNetwork.PluginName `json:"networkPlugin,omitempty"` - VMDefaults VMSpec `json:"vmDefaults,omitempty"` - IDPrefix string `json:"idPrefix,omitempty"` + Runtime igniteRuntime.Name `json:"runtime,omitempty"` + NetworkPlugin igniteNetwork.PluginName `json:"networkPlugin,omitempty"` + VMDefaults VMSpec `json:"vmDefaults,omitempty"` + IDPrefix string `json:"idPrefix,omitempty"` + RegistryConfigDir string `json:"registryConfigDir,omitempty"` } diff --git a/pkg/apis/ignite/v1alpha3/conversion.go b/pkg/apis/ignite/v1alpha3/conversion.go new file mode 100644 index 000000000..1843142c8 --- /dev/null +++ b/pkg/apis/ignite/v1alpha3/conversion.go @@ -0,0 +1,11 @@ +package v1alpha3 + +import ( + "github.com/weaveworks/ignite/pkg/apis/ignite" + "k8s.io/apimachinery/pkg/conversion" +) + +// Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec calls the autogenerated conversion function along with custom conversion logic +func Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(in *ignite.ConfigurationSpec, out *ConfigurationSpec, s conversion.Scope) error { + return autoConvert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(in, out, s) +} diff --git a/pkg/apis/ignite/v1alpha3/zz_generated.conversion.go b/pkg/apis/ignite/v1alpha3/zz_generated.conversion.go index 552fce87b..7baeaddf4 100644 --- a/pkg/apis/ignite/v1alpha3/zz_generated.conversion.go +++ b/pkg/apis/ignite/v1alpha3/zz_generated.conversion.go @@ -48,11 +48,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*ignite.ConfigurationSpec)(nil), (*ConfigurationSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(a.(*ignite.ConfigurationSpec), b.(*ConfigurationSpec), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*FileMapping)(nil), (*ignite.FileMapping)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1alpha3_FileMapping_To_ignite_FileMapping(a.(*FileMapping), b.(*ignite.FileMapping), scope) }); err != nil { @@ -303,6 +298,11 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddConversionFunc((*ignite.ConfigurationSpec)(nil), (*ConfigurationSpec)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(a.(*ignite.ConfigurationSpec), b.(*ConfigurationSpec), scope) + }); err != nil { + return err + } return nil } @@ -376,14 +376,10 @@ func autoConvert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(in *igni return err } out.IDPrefix = in.IDPrefix + // WARNING: in.RegistryConfigDir requires manual conversion: does not exist in peer-type return nil } -// Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec is an autogenerated conversion function. -func Convert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(in *ignite.ConfigurationSpec, out *ConfigurationSpec, s conversion.Scope) error { - return autoConvert_ignite_ConfigurationSpec_To_v1alpha3_ConfigurationSpec(in, out, s) -} - func autoConvert_v1alpha3_FileMapping_To_ignite_FileMapping(in *FileMapping, out *ignite.FileMapping, s conversion.Scope) error { out.HostPath = in.HostPath out.VMPath = in.VMPath diff --git a/pkg/apis/ignite/v1alpha4/types.go b/pkg/apis/ignite/v1alpha4/types.go index f625a3c46..45995c504 100644 --- a/pkg/apis/ignite/v1alpha4/types.go +++ b/pkg/apis/ignite/v1alpha4/types.go @@ -259,8 +259,9 @@ type Configuration struct { // ConfigurationSpec defines the ignite configuration. type ConfigurationSpec struct { - Runtime igniteRuntime.Name `json:"runtime,omitempty"` - NetworkPlugin igniteNetwork.PluginName `json:"networkPlugin,omitempty"` - VMDefaults VMSpec `json:"vmDefaults,omitempty"` - IDPrefix string `json:"idPrefix,omitempty"` + Runtime igniteRuntime.Name `json:"runtime,omitempty"` + NetworkPlugin igniteNetwork.PluginName `json:"networkPlugin,omitempty"` + VMDefaults VMSpec `json:"vmDefaults,omitempty"` + IDPrefix string `json:"idPrefix,omitempty"` + RegistryConfigDir string `json:"registryConfigDir,omitempty"` } diff --git a/pkg/apis/ignite/v1alpha4/zz_generated.conversion.go b/pkg/apis/ignite/v1alpha4/zz_generated.conversion.go index f463c204c..00aadc551 100644 --- a/pkg/apis/ignite/v1alpha4/zz_generated.conversion.go +++ b/pkg/apis/ignite/v1alpha4/zz_generated.conversion.go @@ -361,6 +361,7 @@ func autoConvert_v1alpha4_ConfigurationSpec_To_ignite_ConfigurationSpec(in *Conf return err } out.IDPrefix = in.IDPrefix + out.RegistryConfigDir = in.RegistryConfigDir return nil } @@ -376,6 +377,7 @@ func autoConvert_ignite_ConfigurationSpec_To_v1alpha4_ConfigurationSpec(in *igni return err } out.IDPrefix = in.IDPrefix + out.RegistryConfigDir = in.RegistryConfigDir return nil } diff --git a/pkg/openapi/openapi_generated.go b/pkg/openapi/openapi_generated.go index 8a376bbb5..388cfc712 100644 --- a/pkg/openapi/openapi_generated.go +++ b/pkg/openapi/openapi_generated.go @@ -1955,6 +1955,12 @@ func schema_pkg_apis_ignite_v1alpha4_ConfigurationSpec(ref common.ReferenceCallb Format: "", }, }, + "registryConfigDir": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, }, }, }, diff --git a/pkg/providers/providers.go b/pkg/providers/providers.go index afe61e71f..b9bd45e78 100644 --- a/pkg/providers/providers.go +++ b/pkg/providers/providers.go @@ -37,6 +37,11 @@ var Storage storage.Storage var ComponentConfig *api.Configuration +// RegistryConfigDir is the container runtime registry configuration directory. +// This is used during operations like image import for loading registry +// configurations. +var RegistryConfigDir string + type ProviderInitFunc func() error // Populate initializes all given providers diff --git a/pkg/runtime/auth/auth.go b/pkg/runtime/auth/auth.go index cca1a9f10..8f9628e75 100644 --- a/pkg/runtime/auth/auth.go +++ b/pkg/runtime/auth/auth.go @@ -18,21 +18,24 @@ type AuthCreds func(string) (string, string, error) // NewAuthCreds returns an AuthCreds which loads the credentials from the // docker client config. -func NewAuthCreds(refHostname string) (AuthCreds, error) { +func NewAuthCreds(refHostname string, configPath string) (AuthCreds, string, error) { + log.Debugf("runtime.auth: registry config dir path: %q", configPath) + // Load does not raise an error on ENOENT - dockerConfigFile, err := dockercliconfig.Load("") + dockerConfigFile, err := dockercliconfig.Load(configPath) if err != nil { - return nil, err + return nil, "", err } // DefaultHost converts "docker.io" to "registry-1.docker.io", // which is wanted by credFunc . credFuncExpectedHostname, err := docker.DefaultHost(refHostname) if err != nil { - return nil, err + return nil, "", err } var credFunc AuthCreds + var serverAddress string authConfigHostnames := []string{refHostname} if refHostname == "docker.io" || refHostname == "registry-1.docker.io" { @@ -60,7 +63,7 @@ func NewAuthCreds(refHostname string) (AuthCreds, error) { } else { acsaHostname := credentials.ConvertToHostname(ac.ServerAddress) if acsaHostname != authConfigHostname { - return nil, fmt.Errorf("expected the hostname part of ac.ServerAddress (%q) to be authConfigHostname=%q, got %q", + return nil, "", fmt.Errorf("expected the hostname part of ac.ServerAddress (%q) to be authConfigHostname=%q, got %q", ac.ServerAddress, authConfigHostname, acsaHostname) } } @@ -81,12 +84,13 @@ func NewAuthCreds(refHostname string) (AuthCreds, error) { } return ac.Username, ac.Password, nil } + serverAddress = ac.ServerAddress break } } } // credFunc can be nil here. - return credFunc, nil + return credFunc, serverAddress, nil } func isAuthConfigEmpty(ac dockercliconfigtypes.AuthConfig) bool { diff --git a/pkg/runtime/containerd/client.go b/pkg/runtime/containerd/client.go index 4293578d9..baf23f01b 100644 --- a/pkg/runtime/containerd/client.go +++ b/pkg/runtime/containerd/client.go @@ -2,16 +2,20 @@ package containerd import ( "context" + "crypto/tls" "fmt" "io" "io/ioutil" + "net/http" "os" "os/exec" "path/filepath" "strconv" + "strings" "syscall" "time" + "github.com/docker/cli/cli/config/credentials" meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" "github.com/weaveworks/ignite/pkg/constants" "github.com/weaveworks/ignite/pkg/preflight" @@ -40,6 +44,7 @@ import ( imagespec "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" log "github.com/sirupsen/logrus" + "github.com/weaveworks/ignite/pkg/providers" "golang.org/x/sys/unix" ) @@ -48,6 +53,9 @@ const ( stopTimeoutLabel = "IgniteStopTimeout" logPathTemplate = "/tmp/%s.log" resolvConfName = "runtime.containerd.resolv.conf" + + // InsecureRegistriesEnvVar helps set insecure registries. + InsecureRegistriesEnvVar = "IGNITE_CONTAINERD_INSECURE_REGISTRIES" ) var ( @@ -144,21 +152,48 @@ func GetContainerdClient() (*ctdClient, error) { // newRemoteResolver returns a remote resolver with auth info for a given // host name. -func newRemoteResolver(refHostname string) (remotes.Resolver, error) { +func newRemoteResolver(refHostname string, configPath string) (remotes.Resolver, error) { var authzOpts []docker.AuthorizerOpt - if authCreds, err := auth.NewAuthCreds(refHostname); err != nil { + regOpts := []docker.RegistryOpt{} + insecureAllowed := false + client := &http.Client{} + + // Allow setting insecure_registries through a client-side ENV variable. + // dockerconfig.json does not have a place to set this. + // We would have to override the parser to add a field otherwise. + for _, reg := range strings.Split(os.Getenv(InsecureRegistriesEnvVar), ",") { + // image hostnames don't have protocols, this is the most forgiving parsing logic. + if credentials.ConvertToHostname(reg) == refHostname { + insecureAllowed = true + } + } + + if authCreds, serverAddress, err := auth.NewAuthCreds(refHostname, configPath); err != nil { return nil, err } else { authzOpts = append(authzOpts, docker.WithAuthCreds(authCreds)) + // Allow the dockerconfig.json to specify HTTP as a specific protocol override, defaults to HTTPS + if strings.HasPrefix(serverAddress, "http://") { + if !insecureAllowed { + return nil, fmt.Errorf("Registry %q uses plain HTTP, but is not in the %s env var", serverAddress, InsecureRegistriesEnvVar) + } + regOpts = append(regOpts, docker.WithPlainHTTP(docker.MatchAllHosts)) + } else { + if insecureAllowed { + log.Warnf("Disabling TLS Verification for %q via %s env var", serverAddress, InsecureRegistriesEnvVar) + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + InsecureSkipVerify: true, + }, + } + } + } } authz := docker.NewDockerAuthorizer(authzOpts...) - // TODO: Add plain http option. - regOpts := []docker.RegistryOpt{ - docker.WithAuthorizer(authz), - } + regOpts = append(regOpts, docker.WithAuthorizer(authz)) + regOpts = append(regOpts, docker.WithClient(client)) - // TODO: Add option to skip verifying HTTPS cert. resolverOpts := docker.ResolverOptions{ Hosts: docker.ConfigureDefaultRegistries(regOpts...), } @@ -178,7 +213,7 @@ func (cc *ctdClient) PullImage(image meta.OCIImageRef) error { refDomain := refdocker.Domain(named) // Create a remote resolver for the domain. - resolver, err := newRemoteResolver(refDomain) + resolver, err := newRemoteResolver(refDomain, providers.RegistryConfigDir) if err != nil { return err } diff --git a/pkg/runtime/containerd/client_test.go b/pkg/runtime/containerd/client_test.go index d71bbc499..7f4cec9ef 100644 --- a/pkg/runtime/containerd/client_test.go +++ b/pkg/runtime/containerd/client_test.go @@ -1,6 +1,8 @@ package containerd import ( + "fmt" + "io/ioutil" "os" "path/filepath" "strings" @@ -168,3 +170,82 @@ func TestV2ShimRuntimesHaveBinaryNames(t *testing.T) { } } } + +func TestNewRemoteResolver(t *testing.T) { + // Use a template for the configuration and get a registry configuration + // with appropriate protocol. + templateConfig := ` +{ + "auths": { + "%s://127.5.0.1:5443": { + "auth": "aHR0cHNfdGVzdHVzZXI6aHR0cHNfdGVzdHBhc3N3b3Jk" + } + } +} +` + getRegistryConfigWithProtocol := func(protocol string) string { + return fmt.Sprintf(templateConfig, protocol) + } + + domainRef := "127.5.0.1:5443" + + cases := []struct { + name string + insecureRegistries []string + registryConfig string + wantErr bool + }{ + { + name: "invalid configuration", + registryConfig: ` +{ some invalid json } +`, + wantErr: true, + }, + { + name: "valid configuration", + registryConfig: getRegistryConfigWithProtocol("https"), + }, + { + name: "http server address without insecure registries", + registryConfig: getRegistryConfigWithProtocol("http"), + wantErr: true, + }, + { + name: "http server address with insecure registries", + insecureRegistries: []string{"127.5.0.1:5443"}, + registryConfig: getRegistryConfigWithProtocol("http"), + }, + } + + for _, rt := range cases { + t.Run(rt.name, func(t *testing.T) { + // Create directory for the registry configuration. + dir, err := ioutil.TempDir("", "ignite") + if err != nil { + t.Fatalf("failed to create storage for ignite: %v", err) + } + defer os.RemoveAll(dir) + + // If a registry configuration content is given, write it. + if len(rt.registryConfig) > 0 { + configPath := filepath.Join(dir, "config.json") + writeErr := os.WriteFile(configPath, []byte(rt.registryConfig), 0600) + assert.NilError(t, writeErr) + defer os.Remove(configPath) + } + + // If insecure registries are given, set env vars. + if len(rt.insecureRegistries) > 0 { + irValues := strings.Join(rt.insecureRegistries, ",") + os.Setenv(InsecureRegistriesEnvVar, irValues) + defer os.Unsetenv(InsecureRegistriesEnvVar) + } + + _, rrErr := newRemoteResolver(domainRef, dir) + if (rrErr != nil) != rt.wantErr { + t.Errorf("expected error %t, actual: %v", rt.wantErr, rrErr) + } + }) + } +} diff --git a/pkg/runtime/docker/client.go b/pkg/runtime/docker/client.go index 5fc7fbee4..76b779029 100644 --- a/pkg/runtime/docker/client.go +++ b/pkg/runtime/docker/client.go @@ -21,6 +21,7 @@ import ( meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1" "github.com/weaveworks/ignite/pkg/preflight" "github.com/weaveworks/ignite/pkg/preflight/checkers" + "github.com/weaveworks/ignite/pkg/providers" "github.com/weaveworks/ignite/pkg/runtime" "github.com/weaveworks/ignite/pkg/runtime/auth" "github.com/weaveworks/ignite/pkg/util" @@ -68,7 +69,7 @@ func (dc *dockerClient) PullImage(image meta.OCIImageRef) (err error) { } // Get available credentials from docker cli config. - authCreds, err := auth.NewAuthCreds(refDomain) + authCreds, _, err := auth.NewAuthCreds(refDomain, providers.RegistryConfigDir) if err != nil { return err } diff --git a/vendor/modules.txt b/vendor/modules.txt index 67b7572a3..c302575e0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -563,7 +563,7 @@ golang.org/x/text/transform golang.org/x/text/unicode/bidi golang.org/x/text/unicode/norm golang.org/x/text/width -# golang.org/x/tools v0.1.1 +# golang.org/x/tools v0.1.2 ## explicit golang.org/x/tools/go/ast/astutil golang.org/x/tools/imports