Add registry auth config, and use docker resolver in containerd.

Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
Lantao Liu
2018-07-09 00:58:00 -07:00
parent 5ad95b2db4
commit 952e53bf58
8 changed files with 198 additions and 28 deletions

View File

@@ -444,3 +444,13 @@ func getRuntimeConfigFromContainerInfo(c containers.Container) (criconfig.Runtim
r.Root = runtimeOpts.RuntimeRoot
return r, nil
}
// toRuntimeAuthConfig converts cri plugin auth config to runtime auth config.
func toRuntimeAuthConfig(a criconfig.AuthConfig) *runtime.AuthConfig {
return &runtime.AuthConfig{
Username: a.Username,
Password: a.Password,
Auth: a.Auth,
IdentityToken: a.IdentityToken,
}
}

View File

@@ -19,18 +19,21 @@ package server
import (
"encoding/base64"
"net/http"
"net/url"
"strings"
"github.com/containerd/containerd"
"github.com/containerd/containerd/errdefs"
containerdimages "github.com/containerd/containerd/images"
"github.com/containerd/containerd/reference"
"github.com/containerd/containerd/remotes"
"github.com/containerd/containerd/remotes/docker"
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/net/context"
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
containerdresolver "github.com/containerd/cri/pkg/containerd/resolver"
imagestore "github.com/containerd/cri/pkg/store/image"
"github.com/containerd/cri/pkg/util"
)
@@ -87,12 +90,7 @@ func (c *criService) PullImage(ctx context.Context, r *runtime.PullImageRequest)
if ref != imageRef {
logrus.Debugf("PullImage using normalized image ref: %q", ref)
}
resolver := containerdresolver.NewResolver(containerdresolver.Options{
Credentials: func(string) (string, string, error) { return ParseAuth(r.GetAuth()) },
Client: http.DefaultClient,
Registry: c.getResolverOptions(),
})
_, desc, err := resolver.Resolve(ctx, ref)
resolver, desc, err := c.getResolver(ctx, ref, c.credentials(r.GetAuth()))
if err != nil {
return nil, errors.Wrapf(err, "failed to resolve image %q", ref)
}
@@ -206,10 +204,59 @@ func (c *criService) createImageReference(ctx context.Context, name string, desc
return err
}
func (c *criService) getResolverOptions() map[string][]string {
options := make(map[string][]string)
for ns, mirror := range c.config.Mirrors {
options[ns] = append(options[ns], mirror.Endpoints...)
// credentials returns a credential function for docker resolver to use.
func (c *criService) credentials(auth *runtime.AuthConfig) func(string) (string, string, error) {
return func(host string) (string, string, error) {
if auth == nil {
// Get default auth from config.
for h, ac := range c.config.Registry.Auths {
u, err := url.Parse(h)
if err != nil {
return "", "", errors.Wrapf(err, "parse auth host %q", h)
}
if u.Host == host {
auth = toRuntimeAuthConfig(ac)
break
}
}
}
return ParseAuth(auth)
}
return options
}
// getResolver tries registry mirrors and the default registry, and returns the resolver and descriptor
// from the first working registry.
func (c *criService) getResolver(ctx context.Context, ref string, cred func(string) (string, string, error)) (remotes.Resolver, imagespec.Descriptor, error) {
refspec, err := reference.Parse(ref)
if err != nil {
return nil, imagespec.Descriptor{}, errors.Wrap(err, "parse image reference")
}
// Try mirrors in order first, and then try default host name.
for _, e := range c.config.Registry.Mirrors[refspec.Hostname()].Endpoints {
u, err := url.Parse(e)
if err != nil {
return nil, imagespec.Descriptor{}, errors.Wrapf(err, "parse registry endpoint %q", e)
}
resolver := docker.NewResolver(docker.ResolverOptions{
Credentials: cred,
Client: http.DefaultClient,
Host: func(string) (string, error) { return u.Host, nil },
// By default use "https".
PlainHTTP: u.Scheme == "http",
})
_, desc, err := resolver.Resolve(ctx, ref)
if err == nil {
return resolver, desc, nil
}
// Continue to try next endpoint
}
resolver := docker.NewResolver(docker.ResolverOptions{
Credentials: cred,
Client: http.DefaultClient,
})
_, desc, err := resolver.Resolve(ctx, ref)
if err != nil {
return nil, imagespec.Descriptor{}, errors.Wrap(err, "no available registry endpoint")
}
return resolver, desc, nil
}

View File

@@ -22,6 +22,8 @@ import (
"github.com/stretchr/testify/assert"
runtime "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
criconfig "github.com/containerd/cri/pkg/config"
)
func TestParseAuth(t *testing.T) {
@@ -72,3 +74,58 @@ func TestParseAuth(t *testing.T) {
assert.Equal(t, test.expectedSecret, s)
}
}
func TestCredentials(t *testing.T) {
c := newTestCRIService()
c.config.Registry.Auths = map[string]criconfig.AuthConfig{
"https://test1.io": {
Username: "username1",
Password: "password1",
},
"http://test2.io": {
Username: "username2",
Password: "password2",
},
"//test3.io": {
Username: "username3",
Password: "password3",
},
}
for desc, test := range map[string]struct {
auth *runtime.AuthConfig
host string
expectedUsername string
expectedPassword string
}{
"auth config from CRI should take precedence": {
auth: &runtime.AuthConfig{
Username: "username",
Password: "password",
},
host: "test1.io",
expectedUsername: "username",
expectedPassword: "password",
},
"should support https host": {
host: "test1.io",
expectedUsername: "username1",
expectedPassword: "password1",
},
"should support http host": {
host: "test2.io",
expectedUsername: "username2",
expectedPassword: "password2",
},
"should support hostname only": {
host: "test3.io",
expectedUsername: "username3",
expectedPassword: "password3",
},
} {
t.Logf("TestCase %q", desc)
username, password, err := c.credentials(test.auth)(test.host)
assert.NoError(t, err)
assert.Equal(t, test.expectedUsername, username)
assert.Equal(t, test.expectedPassword, password)
}
}