diff --git a/pkg/server/image_pull.go b/pkg/server/image_pull.go index 23857b4bb..269c09864 100644 --- a/pkg/server/image_pull.go +++ b/pkg/server/image_pull.go @@ -18,9 +18,11 @@ package server import ( gocontext "context" + "encoding/base64" "encoding/json" "fmt" "net/http" + "strings" "sync" "time" @@ -77,7 +79,6 @@ import ( // contents are missing but snapshots are ready, is the image still "READY"? // PullImage pulls an image with authentication config. -// TODO(mikebrow): harden api (including figuring out at what layer we should be blocking on duplicate requests.) func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullImageRequest) (retRes *runtime.PullImageResponse, retErr error) { glog.V(2).Infof("PullImage %q with auth config %+v", r.GetImage().GetImage(), r.GetAuth()) defer func() { @@ -88,10 +89,8 @@ func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullIma }() image := r.GetImage().GetImage() - // TODO(random-liu): [P1] Schema 1 image is not supported in containerd now, we need to support - // it for backward compatiblity. // TODO(mikebrow): add truncIndex for image id - imageID, repoTag, repoDigest, err := c.pullImage(ctx, image) + imageID, repoTag, repoDigest, err := c.pullImage(ctx, image, r.GetAuth()) if err != nil { return nil, fmt.Errorf("failed to pull image %q: %v", image, err) } @@ -173,8 +172,37 @@ func (r *resourceSet) all() map[string]struct{} { return resources } +// ParseAuth parses AuthConfig and returns username and password/secret required by containerd. +func ParseAuth(auth *runtime.AuthConfig) (string, string, error) { + if auth == nil { + 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 "", "", err + } + fields := strings.SplitN(string(decoded), ":", 2) + if len(fields) != 2 { + return "", "", fmt.Errorf("invalid decoded auth: %q", decoded) + } + user, passwd := fields[0], fields[1] + return user, strings.Trim(passwd, "\x00"), nil + } + // TODO(random-liu): Support RegistryToken. + return "", "", fmt.Errorf("invalid auth config") +} + // pullImage pulls image and returns image id (config digest), repoTag and repoDigest. -func (c *criContainerdService) pullImage(ctx context.Context, rawRef string) ( +func (c *criContainerdService) pullImage(ctx context.Context, rawRef string, auth *runtime.AuthConfig) ( // TODO(random-liu): Replace with client.Pull. string, string, string, error) { namedRef, err := normalizeImageRef(rawRef) @@ -189,10 +217,8 @@ func (c *criContainerdService) pullImage(ctx context.Context, rawRef string) ( // Resolve the image reference to get descriptor and fetcher. resolver := docker.NewResolver(docker.ResolverOptions{ - // TODO(random-liu): Add authentication by setting credentials. - // TODO(random-liu): Handle https. - PlainHTTP: true, - Client: http.DefaultClient, + Credentials: func(string) (string, string, error) { return ParseAuth(auth) }, + Client: http.DefaultClient, }) _, desc, err := resolver.Resolve(ctx, ref) if err != nil { diff --git a/pkg/server/image_pull_test.go b/pkg/server/image_pull_test.go index 256855ddc..1272bbeb8 100644 --- a/pkg/server/image_pull_test.go +++ b/pkg/server/image_pull_test.go @@ -17,11 +17,13 @@ limitations under the License. package server import ( + "encoding/base64" "fmt" "sync" "testing" "github.com/stretchr/testify/assert" + "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" "github.com/kubernetes-incubator/cri-containerd/pkg/metadata" ) @@ -93,3 +95,52 @@ func TestResources(t *testing.T) { assert.True(t, ok) } } + +func TestParseAuth(t *testing.T) { + testUser := "username" + testPasswd := "password" + testAuthLen := base64.StdEncoding.EncodedLen(len(testUser + ":" + testPasswd)) + testAuth := make([]byte, testAuthLen) + base64.StdEncoding.Encode(testAuth, []byte(testUser+":"+testPasswd)) + invalidAuth := make([]byte, testAuthLen) + base64.StdEncoding.Encode(invalidAuth, []byte(testUser+"@"+testPasswd)) + for desc, test := range map[string]struct { + auth *runtime.AuthConfig + expectedUser string + expectedSecret string + expectErr bool + }{ + "should not return error if auth config is nil": {}, + "should return error if no supported auth is provided": { + auth: &runtime.AuthConfig{}, + expectErr: true, + }, + "should support identity token": { + auth: &runtime.AuthConfig{IdentityToken: "abcd"}, + expectedSecret: "abcd", + }, + "should support username and password": { + auth: &runtime.AuthConfig{ + Username: testUser, + Password: testPasswd, + }, + expectedUser: testUser, + expectedSecret: testPasswd, + }, + "should support auth": { + auth: &runtime.AuthConfig{Auth: string(testAuth)}, + expectedUser: testUser, + expectedSecret: testPasswd, + }, + "should return error for invalid auth": { + auth: &runtime.AuthConfig{Auth: string(invalidAuth)}, + expectErr: true, + }, + } { + t.Logf("TestCase %q", desc) + u, s, err := ParseAuth(test.auth) + assert.Equal(t, test.expectErr, err != nil) + assert.Equal(t, test.expectedUser, u) + assert.Equal(t, test.expectedSecret, s) + } +}