kaniko/vendor/github.com/isometry/docker-credential-env/env.go

191 lines
4.5 KiB
Go

package main
import (
"bytes"
"context"
"encoding/base64"
"fmt"
"net/url"
"os"
"regexp"
"strings"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials/stscreds"
"github.com/aws/aws-sdk-go-v2/service/ecr"
"github.com/aws/aws-sdk-go-v2/service/sts"
docker_credentials "github.com/docker/docker-credential-helpers/credentials"
)
var ecrHostname = regexp.MustCompile(`^[0-9]+\.dkr\.ecr\.[-a-z0-9]+\.amazonaws\.com$`)
var ghcrHostname = regexp.MustCompile(`^ghcr\.io$`)
const (
defaultScheme = "https://"
envPrefix = "DOCKER"
envUsernameSuffix = "USR"
envPasswordSuffix = "PSW"
envSeparator = "_"
envIgnoreLogin = "IGNORE_DOCKER_LOGIN"
)
type NotSupportedError struct{}
func (m *NotSupportedError) Error() string {
return "not supported"
}
// Env implements the Docker credentials Helper interface.
type Env struct{}
// Add implements the set verb
func (*Env) Add(*docker_credentials.Credentials) error {
switch {
case os.Getenv(envIgnoreLogin) != "":
return nil
default:
return fmt.Errorf("add: %w", &NotSupportedError{})
}
}
// Delete implements the erase verb
func (*Env) Delete(string) error {
switch {
case os.Getenv(envIgnoreLogin) != "":
return nil
default:
return fmt.Errorf("delete: %w", &NotSupportedError{})
}
}
// List implements the list verb
func (*Env) List() (map[string]string, error) {
return nil, fmt.Errorf("list: %w", &NotSupportedError{})
}
// Get implements the get verb
func (e *Env) Get(serverURL string) (username string, password string, err error) {
var (
hostname string
ok bool
)
hostname, err = getHostname(serverURL)
if err != nil {
return
}
if username, password, ok = getEnvCredentials(hostname); ok {
return
}
if ecrHostname.MatchString(hostname) {
// This is an AWS ECR Docker Registry: <account-id>.dkr.ecr.<region>.amazonaws.com
username, password, err = getEcrToken()
return
}
if ghcrHostname.MatchString(hostname) {
// This is a GitHub Container Registry: ghcr.io
if token, found := os.LookupEnv("GITHUB_TOKEN"); found {
username = "github"
password = token
}
return
}
return
}
func getHostname(serverURL string) (hostname string, err error) {
var server *url.URL
server, err = url.Parse(defaultScheme + strings.TrimPrefix(serverURL, defaultScheme))
if err != nil {
return
}
hostname = server.Hostname()
return
}
func getEnvVariables(labels []string, offset int) (envUsername, envPassword string) {
if offset < 0 {
offset = 0
} else if offset > len(labels) {
offset = len(labels)
}
envHostname := strings.Join(labels[offset:], envSeparator)
envUsername = strings.Join([]string{envPrefix, envHostname, envUsernameSuffix}, envSeparator)
envPassword = strings.Join([]string{envPrefix, envHostname, envPasswordSuffix}, envSeparator)
return
}
func getEnvCredentials(hostname string) (username, password string, found bool) {
hostname = strings.ReplaceAll(hostname, "-", "_")
labels := strings.Split(hostname, ".")
for i := 0; i <= len(labels); i++ {
envUsername, envPassword := getEnvVariables(labels, i)
if username, found = os.LookupEnv(envUsername); found {
if password, found = os.LookupEnv(envPassword); found {
break
}
}
}
return
}
func getEcrToken() (username, password string, err error) {
ctx := context.TODO()
cfg, err := config.LoadDefaultConfig(ctx)
if err != nil {
return
}
if roleArn := getRoleArn(cfg.ConfigSources...); roleArn != "" {
stsSvc := sts.NewFromConfig(cfg)
creds := stscreds.NewAssumeRoleProvider(stsSvc, roleArn)
cfg.Credentials = aws.NewCredentialsCache(creds)
}
client := ecr.NewFromConfig(cfg)
output, err := client.GetAuthorizationToken(ctx, nil)
if err != nil {
return
}
for _, authData := range output.AuthorizationData {
// authData.AuthorizationToken is a base64-encoded username:password string,
// where the username is always expected to be "AWS".
var tokenBytes []byte
tokenBytes, err = base64.StdEncoding.DecodeString(*authData.AuthorizationToken)
if err != nil {
return
}
token := bytes.SplitN(tokenBytes, []byte{':'}, 2)
username, password = string(token[0]), string(token[1])
}
return
}
func getRoleArn(configSources ...interface{}) (roleARN string) {
for _, x := range configSources {
switch impl := x.(type) {
case config.EnvConfig:
if impl.RoleARN != "" {
return strings.TrimSpace(impl.RoleARN)
}
case config.SharedConfig:
if impl.RoleARN != "" {
return strings.TrimSpace(impl.RoleARN)
}
}
}
return
}