How to properly authenticate docker client golang library to gcr.io registry?

Viewed 3872

I have a need to programmatically (using golang) login to gcr.io docker registry using this package library https://godoc.org/github.com/docker/docker/client

I have tried using it, i can successfully login but upon pushing an image to my gcr.io project registry, it said

{"errorDetail":{"message":"unauthorized: You don't have the needed permissions to perform this operation, and you may have invalid credentials. To authenticate your request, follow the steps in: https://cloud.google.com/container-registry/docs/advanced-authentication"},"error":"unauthorized: You don't have the needed permissions to perform this operation, and you may have invalid credentials. To authenticate your request, follow the steps in: https://cloud.google.com/container-registry/docs/advanced-authentication"}

My code looks like this

package client
import (
    "context"
    "fmt"
    "io"
    "os"

    "github.com/docker/docker/api/types"
    dockerClient "github.com/docker/docker/client"
)

type Service struct{
    DockerClient *dockerClient.Client
}

type CopyImageOptions struct {
    DestRegistryAuth    string
}
type DockerImageService interface {
    CopyImage(ctx context.Context, source, dest string, option CopyImageOptions)
}

// NewDockerClient returns a client
func NewDockerClient() *Service {
    cli, err := dockerClient.NewEnvClient()
    if err != nil {
        panic(err)
    }
    return &Service{DockerClient: cli}
}

func (s *Service) CopyImage(ctx context.Context, source, dest string, option CopyImageOptions) error {

    rc, err := s.DockerClient.ImagePull(ctx, source, types.ImagePullOptions{})

    if err != nil{
        return fmt.Errorf("error when pulling source image. err: %v", err)
    }
    defer rc.Close()

    io.Copy(os.Stdout, rc)


    destClient := NewDockerClient()

    if option.DestRegistryAuth != ""  {
        //current use case we can assume that the dest is on asia.gcr.io
        status, err := destClient.DockerClient.RegistryLogin(ctx, types.AuthConfig{
            Username:      "oauth2accesstoken",
            Password:      option.DestRegistryAuth,
            ServerAddress: "asia.gcr.io",
        })
        if err != nil{
            return fmt.Errorf("error when login to destination image registry. err: %v", err)
        }

        fmt.Println(status)
    }
    err = destClient.DockerClient.ImageTag(ctx, source, dest)
    if err != nil {
        return fmt.Errorf("error when tagging image. err: %v", err)
    }
    rc, err = destClient.DockerClient.ImagePush(ctx, dest, types.ImagePushOptions{
        RegistryAuth: option.DestRegistryAuth,
    })

    if err != nil{
        return fmt.Errorf("error when pushing image to destionation. err: %v", err)
    }
    defer rc.Close()

    io.Copy(os.Stdout, rc)
    return nil
}

You may take a look at the CopyImage method, where the option.DestRegistryAuth is assigned with the output gcloud auth print-access-token. The username is set to "oauth2accesstoken" because I followed this instruction: https://cloud.google.com/container-registry/docs/advanced-authentication

As for the source parameter, it is assumed it's from public registry like docker.io/library/alpine:3.10, so we can pull it without having configuring any auth token. However for the dest parameter, currently it is an image in my private registry such as: asia.gcr.io/<gcp-project-id>/alpine:3.10

Also, the gcloud auth print-access-token is called after I did gcloud auth loginand I already had full permission to access my private asia.gcr.io registry (assigned on bucket level).

Now the weird thing is I can successfully push it using docker push command, right after do docker login described in here https://cloud.google.com/container-registry/docs/advanced-authentication .

Any advice?

1 Answers

Okay I just found out what the mistake is on my code above. I realized this after looking at example code on pulling image from private registry here: https://docs.docker.com/develop/sdk/examples/#pull-an-image-with-authentication

As it turns out, the RegistryAuth arg in types.ImagePush options expect a base64 encoding string.

So with this code, I can successfully push local image to my private registry.

    authConfig := types.AuthConfig{
        Username: "oauth2accesstoken",
        Password: option.DestRegistryAuth,
    }
    encodedJSON, err := json.Marshal(authConfig)
    if err != nil {
        return fmt.Errorf("error when encoding authConfig. err: %v", err)
    }

    authStr := base64.URLEncoding.EncodeToString(encodedJSON)

    rc, err = destClient.DockerClient.ImagePush(ctx, dest, types.ImagePushOptions{
        RegistryAuth: authStr,
    })
Related