Add pull image authentication.
Signed-off-by: Lantao Liu <lantaol@google.com>
This commit is contained in:
parent
4b53d843bc
commit
d5674be41f
@ -18,9 +18,11 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
gocontext "context"
|
gocontext "context"
|
||||||
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -77,7 +79,6 @@ import (
|
|||||||
// contents are missing but snapshots are ready, is the image still "READY"?
|
// contents are missing but snapshots are ready, is the image still "READY"?
|
||||||
|
|
||||||
// PullImage pulls an image with authentication config.
|
// 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) {
|
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())
|
glog.V(2).Infof("PullImage %q with auth config %+v", r.GetImage().GetImage(), r.GetAuth())
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -88,10 +89,8 @@ func (c *criContainerdService) PullImage(ctx context.Context, r *runtime.PullIma
|
|||||||
}()
|
}()
|
||||||
image := r.GetImage().GetImage()
|
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
|
// 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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to pull image %q: %v", image, err)
|
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
|
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.
|
// 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.
|
// TODO(random-liu): Replace with client.Pull.
|
||||||
string, string, string, error) {
|
string, string, string, error) {
|
||||||
namedRef, err := normalizeImageRef(rawRef)
|
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.
|
// Resolve the image reference to get descriptor and fetcher.
|
||||||
resolver := docker.NewResolver(docker.ResolverOptions{
|
resolver := docker.NewResolver(docker.ResolverOptions{
|
||||||
// TODO(random-liu): Add authentication by setting credentials.
|
Credentials: func(string) (string, string, error) { return ParseAuth(auth) },
|
||||||
// TODO(random-liu): Handle https.
|
Client: http.DefaultClient,
|
||||||
PlainHTTP: true,
|
|
||||||
Client: http.DefaultClient,
|
|
||||||
})
|
})
|
||||||
_, desc, err := resolver.Resolve(ctx, ref)
|
_, desc, err := resolver.Resolve(ctx, ref)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -17,11 +17,13 @@ limitations under the License.
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
"k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||||
|
|
||||||
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
"github.com/kubernetes-incubator/cri-containerd/pkg/metadata"
|
||||||
)
|
)
|
||||||
@ -93,3 +95,52 @@ func TestResources(t *testing.T) {
|
|||||||
assert.True(t, ok)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user