Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

[feature] implement kim-native credentials #71

Merged
merged 1 commit into from
Sep 1, 2021
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
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/gogo/protobuf v1.3.2
github.com/golang/protobuf v1.4.3
github.com/moby/buildkit v0.8.3
github.com/moby/term v0.0.0-20200915141129-7f0af18e79f2
github.com/opencontainers/image-spec v1.0.1
github.com/pkg/errors v0.9.1
github.com/rancher/wrangler v0.7.3-0.20201002224307-4303c423125a
Expand Down
2 changes: 2 additions & 0 deletions pkg/cli/command/builder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"

"github.com/rancher/kim/pkg/cli/command/builder/install"
"github.com/rancher/kim/pkg/cli/command/builder/login"
"github.com/rancher/kim/pkg/cli/command/builder/uninstall"
wrangler "github.com/rancher/wrangler-cli"
"github.com/spf13/cobra"
Expand All @@ -26,6 +27,7 @@ func Command() *cobra.Command {
cmd.AddCommand(
install.Command(),
uninstall.Command(),
login.Command(),
)
return cmd
}
Expand Down
99 changes: 99 additions & 0 deletions pkg/cli/command/builder/login/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package login

import (
"bufio"
"fmt"
"io/ioutil"
"net/url"
"os"
"strings"

"github.com/moby/term"
"github.com/pkg/errors"
"github.com/rancher/kim/pkg/client"
"github.com/rancher/kim/pkg/client/builder"
wrangler "github.com/rancher/wrangler-cli"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/credentialprovider"
)

func Command() *cobra.Command {
return wrangler.Command(&CommandSpec{}, cobra.Command{
Use: "login [OPTIONS] [SERVER]",
Short: "Establish credentials for a registry.",
DisableFlagsInUseLine: true,
Args: cobra.ExactArgs(1),
})
}

type CommandSpec struct {
builder.Login
}

func (s *CommandSpec) Run(cmd *cobra.Command, args []string) error {
k8s, err := client.DefaultConfig.Interface()
if err != nil {
return err
}
if s.PasswordStdin {
if s.Password != "" {
return errors.New("--password and --password-stdin are mutually exclusive")
}
if s.Username == "" {
return errors.New("must provide --username with --password-stdin")
}
password, err := ioutil.ReadAll(cmd.InOrStdin())
if err != nil {
return err
}
s.Password = strings.TrimSuffix(string(password), "\n")
s.Password = strings.TrimSuffix(s.Password, "\r")
}
if (s.Username == "" || s.Password == "") && !term.IsTerminal(os.Stdout.Fd()) {
return errors.New("cannot perform interactive login from non tty device")
}
if s.Username == "" {
fmt.Fprintf(os.Stdout, "Username: ")
reader := bufio.NewReader(os.Stdin)
line, _, err := reader.ReadLine()
if err != nil {
return err
}
s.Username = strings.TrimSpace(string(line))
}
if s.Password == "" {
state, err := term.SaveState(os.Stdin.Fd())
if err != nil {
return err
}
fmt.Fprintf(os.Stdout, "Password: ")
term.DisableEcho(os.Stdin.Fd(), state)
reader := bufio.NewReader(os.Stdin)
line, _, err := reader.ReadLine()
if err != nil {
return err
}
fmt.Fprintln(os.Stdout)
term.RestoreTerminal(os.Stdin.Fd(), state)
s.Password = strings.TrimSpace(string(line))
if s.Password == "" {
return errors.New("password is required")
}
}
server, err := credentialprovider.ParseSchemelessURL(args[0])
if err != nil {
if server, err = url.Parse(args[0]); err != nil {
return err
}
}
// special case for [*.]docker.io -> https://index.docker.io/v1/
if strings.HasSuffix(server.Host, "docker.io") {
server.Scheme = "https"
server.Host = "index.docker.io"
if server.Path == "" {
server.Path = "/v1/"
}
return s.Login.Do(cmd.Context(), k8s, server.String())
}
return s.Login.Do(cmd.Context(), k8s, server.Host)
}
73 changes: 73 additions & 0 deletions pkg/client/builder/login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package builder

import (
"context"
"encoding/json"

"github.com/rancher/kim/pkg/client"
corev1 "k8s.io/api/core/v1"
apierr "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/client-go/util/retry"
"k8s.io/kubernetes/pkg/credentialprovider"
)

type Login struct {
Password string `usage:"Password" short:"p"`
PasswordStdin bool `usage:"Take the password from stdin"`
Username string `usage:"Username" short:"u"`
}

func (s *Login) Do(_ context.Context, k *client.Interface, server string) error {
return retry.RetryOnConflict(retry.DefaultRetry, func() error {
login, err := k.Core.Secret().Get(k.Namespace, "kim-docker-config", metav1.GetOptions{})
if apierr.IsNotFound(err) {
dockerConfigJSON := credentialprovider.DockerConfigJSON{
Auths: map[string]credentialprovider.DockerConfigEntry{
server: {
Username: s.Username,
Password: s.Password,
},
},
}
dockerConfigJSONBytes, err := json.Marshal(&dockerConfigJSON)
if err != nil {
return err
}
login = &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "kim-docker-config",
Namespace: k.Namespace,
Labels: labels.Set{
"app.kubernetes.io/managed-by": "kim",
},
},
Type: corev1.SecretTypeDockerConfigJson,
Data: map[string][]byte{
corev1.DockerConfigJsonKey: dockerConfigJSONBytes,
},
}
_, err = k.Core.Secret().Create(login)
return err
}
var dockerConfigJSON credentialprovider.DockerConfigJSON
if dockerConfigJSONBytes, ok := login.Data[corev1.DockerConfigJsonKey]; ok {
if err := json.Unmarshal(dockerConfigJSONBytes, &dockerConfigJSON); err != nil {
return err
}
}
dockerConfigJSON.Auths[server] = credentialprovider.DockerConfigEntry{
Username: s.Username,
Password: s.Password,
}
dockerConfigJSONBytes, err := json.Marshal(&dockerConfigJSON)
if err != nil {
return err
}
login.Type = corev1.SecretTypeDockerConfigJson
login.Data[corev1.DockerConfigJsonKey] = dockerConfigJSONBytes
_, err = k.Core.Secret().Update(login)
return err
})
}
18 changes: 18 additions & 0 deletions pkg/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,11 @@ import (
rbacctl "github.com/rancher/wrangler/pkg/generated/controllers/rbac"
rbacctlv1 "github.com/rancher/wrangler/pkg/generated/controllers/rbac/v1"
"github.com/rancher/wrangler/pkg/kubeconfig"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/pkg/credentialprovider"
"k8s.io/kubernetes/pkg/credentialprovider/secrets"
)

const (
Expand Down Expand Up @@ -127,3 +131,17 @@ func GetServiceAddress(_ context.Context, k8s *Interface, port string) (string,
}
return "", errors.New("unknown service port")
}

func GetDockerKeyring(_ context.Context, k8s *Interface) credentialprovider.DockerKeyring {
secret, err := k8s.Core.Secret().Get(k8s.Namespace, "kim-docker-config", metav1.GetOptions{})
if err != nil {
logrus.Debug(err)
return credentialprovider.NewDockerKeyring()
}
keyring, err := secrets.MakeDockerKeyring([]corev1.Secret{*secret}, nil)
if err != nil {
logrus.Debug(err)
return credentialprovider.NewDockerKeyring()
}
return keyring
}
23 changes: 22 additions & 1 deletion pkg/client/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (

buildkit "github.com/moby/buildkit/client"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
Expand All @@ -21,7 +22,7 @@ func Control(ctx context.Context, k8s *Interface, fn ControlFunc) error {
return err
}

tmp, err := ioutil.TempDir("", "kim-tls-*")
tmp, err := ioutil.TempDir("", "kim-private-*")
if err != nil {
return errors.Wrap(err, "failed to create temp directory")
}
Expand Down Expand Up @@ -64,6 +65,26 @@ func Control(ctx context.Context, k8s *Interface, fn ControlFunc) error {
}
}

// docker-config
secret, err = k8s.Core.Secret().Get(k8s.Namespace, "kim-docker-config", metav1.GetOptions{})
switch {
case err != nil:
logrus.Debugf("skipping kim-docker-config with error: %v", err)
case secret.Type != corev1.SecretTypeDockerConfigJson:
logrus.Warnf("skipping kim-docker-config with unsupported type: %s", secret.Type)
case secret.Type == corev1.SecretTypeDockerConfigJson:
if dockerConfigJSONBytes, ok := secret.Data[corev1.DockerConfigJsonKey]; ok {
if err := ioutil.WriteFile(filepath.Join(tmp, "config.json"), dockerConfigJSONBytes, 0600); err != nil {
return errors.Wrap(err, "failed to write docker config")
}
if err := os.Setenv("DOCKER_CONFIG", tmp); err != nil {
return errors.Wrap(err, "failed to setup docker config")
}
} else {
logrus.Warnf("skipping kim-docker-config with missing value %s", corev1.DockerConfigJsonKey)
}
}

bkc, err := buildkit.New(ctx, fmt.Sprintf("tcp://%s", addr), options...)
if err != nil {
return err
Expand Down
3 changes: 1 addition & 2 deletions pkg/client/image/pull.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/credentialprovider"
)

type Pull struct {
Expand Down Expand Up @@ -74,7 +73,7 @@ func (s *Pull) Do(ctx context.Context, k8s *client.Interface, image string) erro
if s.Cri {
req.Image.Annotations["images.cattle.io/pull-backend"] = "cri"
}
keyring := credentialprovider.NewDockerKeyring()
keyring := client.GetDockerKeyring(ctx, k8s)
if auth, ok := keyring.Lookup(image); ok {
req.Auth = &criv1.AuthConfig{
Username: auth[0].Username,
Expand Down
3 changes: 1 addition & 2 deletions pkg/client/image/push.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
criv1 "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
"k8s.io/kubernetes/pkg/credentialprovider"
)

type Push struct {
Expand Down Expand Up @@ -58,7 +57,7 @@ func (s *Push) Do(ctx context.Context, k8s *client.Interface, image string) erro
Image: image,
},
}
keyring := credentialprovider.NewDockerKeyring()
keyring := client.GetDockerKeyring(ctx, k8s)
if auth, ok := keyring.Lookup(image); ok {
req.Auth = &criv1.AuthConfig{
Username: auth[0].Username,
Expand Down