| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272 |
- package helper
- import (
- "context"
- "encoding/base64"
- "fmt"
- "log"
- "net/url"
- "os"
- "path/filepath"
- "regexp"
- "strings"
- "time"
- "github.com/docker/docker-credential-helpers/credentials"
- "github.com/porter-dev/porter/cli/cmd"
- "github.com/porter-dev/porter/cli/cmd/api"
- "github.com/spf13/viper"
- "k8s.io/client-go/util/homedir"
- )
- // PorterHelper implements credentials.Helper: it acts as a credentials
- // helper for Docker that allows authentication with different registries.
- type PorterHelper struct {
- Debug bool
- credCache CredentialsCache
- }
- // Add appends credentials to the store.
- func (p *PorterHelper) Add(cr *credentials.Credentials) error {
- // Doesn't seem to be called
- return nil
- }
- // Delete removes credentials from the store.
- func (p *PorterHelper) Delete(serverURL string) error {
- // Doesn't seem to be called
- return nil
- }
- var ecrPattern = regexp.MustCompile(`(^[a-zA-Z0-9][a-zA-Z0-9-_]*)\.dkr\.ecr(\-fips)?\.([a-zA-Z0-9][a-zA-Z0-9-_]*)\.amazonaws\.com(\.cn)?`)
- // Get retrieves credentials from the store.
- // It returns username and secret as strings.
- func (p *PorterHelper) Get(serverURL string) (user string, secret string, err error) {
- p.init()
- if strings.Contains(serverURL, "gcr.io") {
- return p.getGCR(serverURL)
- } else if strings.Contains(serverURL, "registry.digitalocean.com") {
- return p.getDOCR(serverURL)
- }
- return p.getECR(serverURL)
- }
- func (p *PorterHelper) getGCR(serverURL string) (user string, secret string, err error) {
- urlP, err := url.Parse("https://" + serverURL)
- if err != nil {
- return "", "", err
- }
- credCache := BuildCredentialsCache(urlP.Host)
- cachedEntry := credCache.Get(serverURL)
- var token string
- if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
- token = cachedEntry.AuthorizationToken
- } else {
- projID := viper.GetUint("project")
- client := cmd.GetAPIClient()
- // get a token from the server
- tokenResp, err := client.GetGCRAuthorizationToken(context.Background(), projID, &api.GetGCRTokenRequest{
- ServerURL: serverURL,
- })
- if err != nil {
- return "", "", err
- }
- token = tokenResp.Token
- // set the token in cache
- credCache.Set(serverURL, &AuthEntry{
- AuthorizationToken: token,
- RequestedAt: time.Now(),
- ExpiresAt: *tokenResp.ExpiresAt,
- ProxyEndpoint: serverURL,
- })
- }
- return "oauth2accesstoken", token, nil
- }
- func (p *PorterHelper) getDOCR(serverURL string) (user string, secret string, err error) {
- urlP, err := url.Parse("https://" + serverURL)
- if err != nil {
- if p.Debug {
- log.Printf("Error: %s\n", err.Error())
- }
- return "", "", err
- }
- credCache := BuildCredentialsCache(urlP.Host)
- cachedEntry := credCache.Get(serverURL)
- var token string
- if p.Debug {
- log.Printf("GETTING FROM DOCR", urlP)
- }
- if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
- token = cachedEntry.AuthorizationToken
- if p.Debug {
- log.Printf("USING CACHED TOKEN", token)
- }
- } else {
- host := viper.GetString("host")
- projID := viper.GetUint("project")
- client := cmd.GetAPIClient()
- if p.Debug {
- log.Printf("MAKING REQUEST", host, projID)
- }
- // get a token from the server
- tokenResp, err := client.GetDOCRAuthorizationToken(context.Background(), projID, &api.GetDOCRTokenRequest{
- ServerURL: serverURL,
- })
- if err != nil {
- if p.Debug {
- log.Printf("Error: %s\n", err.Error())
- }
- return "", "", err
- }
- token = tokenResp.Token
- if t := *tokenResp.ExpiresAt; len(token) > 0 && !t.IsZero() {
- // set the token in cache
- credCache.Set(serverURL, &AuthEntry{
- AuthorizationToken: token,
- RequestedAt: time.Now(),
- ExpiresAt: t,
- ProxyEndpoint: serverURL,
- })
- }
- }
- return token, token, nil
- }
- func (p *PorterHelper) getECR(serverURL string) (user string, secret string, err error) {
- // parse the server url for region
- matches := ecrPattern.FindStringSubmatch(serverURL)
- if len(matches) == 0 {
- err := fmt.Errorf("only ECR registry URLs are supported")
- if p.Debug {
- log.Printf("Error: %s\n", err.Error())
- }
- return "", "", err
- } else if len(matches) < 3 {
- err := fmt.Errorf("%s is not a valid ECR repository URI", serverURL)
- if p.Debug {
- log.Printf("Error: %s\n", err.Error())
- }
- return "", "", err
- }
- region := matches[3]
- credCache := BuildCredentialsCache(region)
- cachedEntry := credCache.Get(serverURL)
- var token string
- if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
- token = cachedEntry.AuthorizationToken
- } else {
- projID := viper.GetUint("project")
- client := cmd.GetAPIClient()
- // get a token from the server
- tokenResp, err := client.GetECRAuthorizationToken(context.Background(), projID, matches[3])
- if err != nil {
- return "", "", err
- }
- token = tokenResp.Token
- // set the token in cache
- credCache.Set(serverURL, &AuthEntry{
- AuthorizationToken: token,
- RequestedAt: time.Now(),
- ExpiresAt: *tokenResp.ExpiresAt,
- ProxyEndpoint: serverURL,
- })
- }
- return p.getAuth(token)
- }
- // List returns the stored serverURLs and their associated usernames.
- func (p *PorterHelper) List() (map[string]string, error) {
- p.init()
- credCache := BuildCredentialsCache("")
- entries := credCache.List()
- res := make(map[string]string)
- for _, entry := range entries {
- user, _, err := p.getAuth(entry.AuthorizationToken)
- if err != nil {
- continue
- }
- res[entry.ProxyEndpoint] = user
- }
- return res, nil
- }
- func (p *PorterHelper) getAuth(token string) (string, string, error) {
- decodedToken, err := base64.StdEncoding.DecodeString(token)
- if err != nil {
- return "", "", fmt.Errorf("Invalid token: %v", err)
- }
- parts := strings.SplitN(string(decodedToken), ":", 2)
- if len(parts) < 2 {
- return "", "", fmt.Errorf("Invalid token: expected two parts, got %d", len(parts))
- }
- return parts[0], parts[1], nil
- }
- func (p *PorterHelper) init() {
- cmd.Setup()
- if p.Debug {
- var home = homedir.HomeDir()
- file, err := os.OpenFile(filepath.Join(home, ".porter", "logs.txt"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
- if err == nil {
- log.SetOutput(file)
- }
- }
- }
|