Skip to content

Commit 75e6390

Browse files
authored
Merge pull request #85 from AkihiroSuda/dev
support `--insecure-registry`
2 parents 8f6094f + 6760ebf commit 75e6390

13 files changed

Lines changed: 342 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ Show the nerdctl version information
448448
- :nerd_face: `--cni-netconfpath`: CNI netconf path (default: `/etc/cni/net.d`) [`$NETCONFPATH`]
449449
- :nerd_face: `--data-root`: nerdctl data root, e.g. "/var/lib/nerdctl"
450450
- :nerd_face: `--cgroup-manager=(cgroupfs|systemd)`: cgroup manager
451+
- :nerd_face: `--insecure-registry`: skips verifying HTTPS certs, and allows falling back to plain HTTP
451452

452453
## Unimplemented Docker commands
453454
Container management:

Vagrantfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ Vagrant.configure("2") do |config|
99
config.vm.provider :virtualbox do |v|
1010
v.memory = memory
1111
v.cpus = cpus
12+
# The default CIDR conflicts with slirp4netns CIDR (10.0.2.0/24)
13+
v.customize ["modifyvm", :id, "--natnet1", "192.168.42.0/24"]
1214
end
1315
config.vm.provider :libvirt do |v|
1416
v.memory = memory

docs/registry.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,19 @@
33
nerdctl uses `${DOCKER_CONFIG}/config.json` for the authentication with image registries.
44

55
`$DOCKER_CONFIG` defaults to `$HOME/.docker`.
6+
7+
## Using insecure registry
8+
9+
If you face `http: server gave HTTP response to HTTPS client` and you cannot configure TLS for the registry, try `--insecure-registry` flag:
10+
11+
e.g.,
12+
```console
13+
$ nerdctl --insecure-registry run --rm 192.168.12.34:5000/foo
14+
```
15+
16+
## Accessing 127.0.0.1 from rootless nerdctl
17+
18+
Currently, rootless nerdctl cannot pull images from 127.0.0.1, because
19+
the pull operation occurs in RootlessKit's network namespace.
20+
21+
See https://github.com/AkihiroSuda/nerdctl/issues/86 for the discussion about workarounds.

main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,10 @@ func newApp() *cli.App {
110110
Usage: "Cgroup manager to use (\"cgroupfs\"|\"systemd\")",
111111
Value: ncdefaults.CgroupManager(),
112112
},
113+
&cli.BoolFlag{
114+
Name: "insecure-registry",
115+
Usage: "skips verifying HTTPS certs, and allows falling back to plain HTTP",
116+
},
113117
}
114118
app.Before = func(clicontext *cli.Context) error {
115119
if debug {

pkg/imgutil/dockerconfigresolver/dockerconfigresolver.go

Lines changed: 71 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
package dockerconfigresolver
1919

2020
import (
21+
"crypto/tls"
2122
"net"
23+
"net/http"
2224
"net/url"
2325

2426
"github.com/containerd/containerd/remotes"
@@ -29,12 +31,77 @@ import (
2931
"github.com/sirupsen/logrus"
3032
)
3133

34+
type opts struct {
35+
plainHTTP bool
36+
skipVerifyCerts bool
37+
}
38+
39+
// Opt for New
40+
type Opt func(*opts)
41+
42+
// WithPlainHTTP enables insecure plain HTTP
43+
func WithPlainHTTP(b bool) Opt {
44+
return func(o *opts) {
45+
o.plainHTTP = b
46+
}
47+
}
48+
49+
// WithSkipVerifyCerts skips verifying TLS certs
50+
func WithSkipVerifyCerts(b bool) Opt {
51+
return func(o *opts) {
52+
o.skipVerifyCerts = b
53+
}
54+
}
55+
3256
// New instantiates a resolver using $DOCKER_CONFIG/config.json .
3357
//
3458
// $DOCKER_CONFIG defaults to "~/.docker".
3559
//
3660
// refHostname is like "docker.io".
37-
func New(refHostname string) (remotes.Resolver, error) {
61+
func New(refHostname string, optFuncs ...Opt) (remotes.Resolver, error) {
62+
var o opts
63+
for _, of := range optFuncs {
64+
of(&o)
65+
}
66+
var authzOpts []docker.AuthorizerOpt
67+
if authCreds, err := NewAuthCreds(refHostname); err != nil {
68+
return nil, err
69+
} else {
70+
authzOpts = append(authzOpts, docker.WithAuthCreds(authCreds))
71+
}
72+
authz := docker.NewDockerAuthorizer(authzOpts...)
73+
plainHTTPFunc := docker.MatchLocalhost
74+
if o.plainHTTP {
75+
plainHTTPFunc = docker.MatchAllHosts
76+
}
77+
regOpts := []docker.RegistryOpt{
78+
docker.WithAuthorizer(authz),
79+
docker.WithPlainHTTP(plainHTTPFunc),
80+
}
81+
if o.skipVerifyCerts {
82+
tr := &http.Transport{
83+
TLSClientConfig: &tls.Config{
84+
InsecureSkipVerify: true,
85+
},
86+
}
87+
client := &http.Client{
88+
Transport: tr,
89+
}
90+
regOpts = append(regOpts, docker.WithClient(client))
91+
}
92+
resovlerOpts := docker.ResolverOptions{
93+
Hosts: docker.ConfigureDefaultRegistries(regOpts...),
94+
}
95+
resolver := docker.NewResolver(resovlerOpts)
96+
return resolver, nil
97+
}
98+
99+
// AuthCreds is for docker.WithAuthCreds
100+
type AuthCreds func(string) (string, string, error)
101+
102+
// NewAuthCreds returns AuthCreds that uses $DOCKER_CONFIG/config.json .
103+
// AuthCreds can be nil.
104+
func NewAuthCreds(refHostname string) (AuthCreds, error) {
38105
// Load does not raise an error on ENOENT
39106
dockerConfigFile, err := dockercliconfig.Load("")
40107
if err != nil {
@@ -48,7 +115,7 @@ func New(refHostname string) (remotes.Resolver, error) {
48115
return nil, err
49116
}
50117

51-
var credFunc func(string) (string, string, error)
118+
var credFunc AuthCreds
52119

53120
authConfigHostnames := []string{refHostname}
54121
if refHostname == "docker.io" || refHostname == "registry-1.docker.io" {
@@ -110,13 +177,8 @@ func New(refHostname string) (remotes.Resolver, error) {
110177
}
111178
}
112179
}
113-
114-
resovlerOpts := docker.ResolverOptions{
115-
// FIXME: Credentials is deprecated
116-
Credentials: credFunc,
117-
}
118-
resolver := docker.NewResolver(resovlerOpts)
119-
return resolver, nil
180+
// credsFunc can be nil here
181+
return credFunc, nil
120182
}
121183

122184
func isAuthConfigEmpty(ac dockercliconfigtypes.AuthConfig) bool {

pkg/imgutil/imgutil.go

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/AkihiroSuda/nerdctl/pkg/imgutil/pull"
2727
"github.com/containerd/containerd"
2828
refdocker "github.com/containerd/containerd/reference/docker"
29+
"github.com/containerd/containerd/remotes"
2930
"github.com/containerd/stargz-snapshotter/fs/source"
3031
"github.com/pkg/errors"
3132
"github.com/sirupsen/logrus"
@@ -41,7 +42,10 @@ type EnsuredImage struct {
4142
// PullMode is either one of "always", "missing", "never"
4243
type PullMode = string
4344

44-
func EnsureImage(ctx context.Context, client *containerd.Client, stdout io.Writer, snapshotter, rawRef string, mode PullMode) (*EnsuredImage, error) {
45+
// EnsureImage ensures the image.
46+
//
47+
// When insecure is set, skips verifying certs, and also falls back to HTTP when the registry does not speak HTTPS
48+
func EnsureImage(ctx context.Context, client *containerd.Client, stdout io.Writer, snapshotter, rawRef string, mode PullMode, insecure bool) (*EnsuredImage, error) {
4549
named, err := refdocker.ParseDockerRef(rawRef)
4650
if err != nil {
4751
return nil, err
@@ -70,11 +74,50 @@ func EnsureImage(ctx context.Context, client *containerd.Client, stdout io.Write
7074
return nil, errors.Errorf("image %q is not available", rawRef)
7175
}
7276

73-
resolver, err := dockerconfigresolver.New(refdocker.Domain(named))
77+
refDomain := refdocker.Domain(named)
78+
79+
var dOpts []dockerconfigresolver.Opt
80+
if insecure {
81+
logrus.Warnf("skipping verifying HTTPS certs for %q", refDomain)
82+
dOpts = append(dOpts, dockerconfigresolver.WithSkipVerifyCerts(true))
83+
}
84+
resolver, err := dockerconfigresolver.New(refDomain, dOpts...)
7485
if err != nil {
7586
return nil, err
7687
}
7788

89+
img, err := pullImage(ctx, client, stdout, snapshotter, resolver, ref)
90+
if err != nil {
91+
if !IsErrHTTPResponseToHTTPSClient(err) {
92+
return nil, err
93+
}
94+
if insecure {
95+
logrus.WithError(err).Warnf("server %q does not seem to support HTTPS, falling back to plain HTTP", refDomain)
96+
dOpts = append(dOpts, dockerconfigresolver.WithPlainHTTP(true))
97+
resolver, err = dockerconfigresolver.New(refDomain, dOpts...)
98+
if err != nil {
99+
return nil, err
100+
}
101+
return pullImage(ctx, client, stdout, snapshotter, resolver, ref)
102+
} else {
103+
logrus.WithError(err).Errorf("server %q does not seem to support HTTPS", refDomain)
104+
logrus.Info("Hint: you may want to try --insecure-registry to allow plain HTTP (if you are in a trusted network)")
105+
return nil, err
106+
}
107+
}
108+
return img, nil
109+
}
110+
111+
// IsErrHTTPResponseToHTTPSClient returns whether err is
112+
// "http: server gave HTTP response to HTTPS client"
113+
func IsErrHTTPResponseToHTTPSClient(err error) bool {
114+
// The error string is unexposed as of Go 1.16, so we can't use `errors.Is`.
115+
// https://github.com/golang/go/issues/44855
116+
const unexposed = "server gave HTTP response to HTTPS client"
117+
return strings.Contains(err.Error(), unexposed)
118+
}
119+
120+
func pullImage(ctx context.Context, client *containerd.Client, stdout io.Writer, snapshotter string, resolver remotes.Resolver, ref string) (*EnsuredImage, error) {
78121
ctx, done, err := client.WithLease(ctx)
79122
if err != nil {
80123
return nil, err
@@ -109,6 +152,7 @@ func EnsureImage(ctx context.Context, client *containerd.Client, stdout io.Write
109152
Remote: sgz,
110153
}
111154
return res, nil
155+
112156
}
113157

114158
func isStargz(sn string) bool {

pkg/imgutil/pull/pull.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ func Pull(ctx context.Context, client *containerd.Client, ref string, config *Co
7373
}
7474
opts = append(opts, config.RemoteOpts...)
7575

76+
// client.Pull is for single-platform (TODO: support multi)
7677
img, err := client.Pull(pctx, ref, opts...)
7778
stopProgress()
7879
if err != nil {

pkg/imgutil/push/push.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,16 @@ import (
3232
"github.com/containerd/containerd/images"
3333
"github.com/containerd/containerd/log"
3434
"github.com/containerd/containerd/pkg/progress"
35+
"github.com/containerd/containerd/platforms"
3536
"github.com/containerd/containerd/remotes"
3637
"github.com/containerd/containerd/remotes/docker"
3738
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
3839
"github.com/pkg/errors"
3940
"golang.org/x/sync/errgroup"
4041
)
4142

42-
func Push(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, stdout io.Writer, localRef, remoteRef string) error {
43+
func Push(ctx context.Context, client *containerd.Client, resolver remotes.Resolver, stdout io.Writer,
44+
localRef, remoteRef string, platform platforms.MatchComparer) error {
4345
img, err := client.ImageService().Get(ctx, localRef)
4446
if err != nil {
4547
return errors.Wrap(err, "unable to resolve image to manifest")
@@ -66,6 +68,7 @@ func Push(ctx context.Context, client *containerd.Client, resolver remotes.Resol
6668
return client.Push(ctx, remoteRef, desc,
6769
containerd.WithResolver(resolver),
6870
containerd.WithImageHandler(jobHandler),
71+
containerd.WithPlatformMatcher(platform),
6972
)
7073
})
7174

pkg/testutil/testutil.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,5 @@ const (
214214
AlpineImage = "mirror.gcr.io/library/alpine:3.13"
215215
NginxAlpineImage = "mirror.gcr.io/library/nginx:1.19-alpine"
216216
NginxAlpineIndexHTMLSnippet = "<title>Welcome to nginx!</title>"
217+
RegistryImage = "mirror.gcr.io/library/registry:2"
217218
)

pull.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ func pullAction(clicontext *cli.Context) error {
4040
return err
4141
}
4242
defer cancel()
43-
_, err = imgutil.EnsureImage(ctx, client, clicontext.App.Writer, clicontext.String("snapshotter"), clicontext.Args().First(), "always")
43+
insecure := clicontext.Bool("insecure-registry")
44+
_, err = imgutil.EnsureImage(ctx, client, clicontext.App.Writer, clicontext.String("snapshotter"), clicontext.Args().First(),
45+
"always", insecure)
4446
return err
4547
}

0 commit comments

Comments
 (0)