Skip to content
This repository was archived by the owner on Mar 9, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 35 additions & 9 deletions pkg/server/image_pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ package server

import (
gocontext "context"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strings"
"sync"
"time"

Expand Down Expand Up @@ -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() {
Expand All @@ -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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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 {
Expand Down
51 changes: 51 additions & 0 deletions pkg/server/image_pull_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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))
Copy link
Member

@mikebrow mikebrow Jun 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add a bad auth here to get closer to 100% test coverage on the api

For example:
invalidAuthLen := base64.StdEncoding.EncodedLen(len(testUser + "%" + testPasswd))
invalidDecodeAuth := make([]byte, invalidAuthLen)
base64.StdEncoding.Encode(invalidDecodeAuth, []byte(testUser+"%"+testPasswd)) // % is an invalid base64 char...and an invalid separator

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

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)
}
}