Agung Pratama
Agung Pratama

Reputation: 3794

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

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?

Upvotes: 5

Views: 6582

Answers (2)

Kerren
Kerren

Reputation: 580

For anyone landing on this now, Agung's comment is still 90% correct, however, they've moved the AuthConfig into the registry types. So you now need to have:

import (
  "github.com/docker/docker/api/types/registry"
)

Then in your code, you'd use:

func YourFunction(username string, password string) {
  // some code
  authConfig := registry.AuthConfig{
    Username: username,
    Password: password,
  }
  // other code
}

One final note is that you no longer pass this in to the cleint, you pass it into the functions you call as an option.

The link given by Agung has the updated information.

Upvotes: 0

Agung Pratama
Agung Pratama

Reputation: 3794

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

Upvotes: 6

Related Questions