helper.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. package helper
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "log"
  7. "os"
  8. "path/filepath"
  9. "regexp"
  10. "strings"
  11. "time"
  12. "github.com/docker/docker-credential-helpers/credentials"
  13. "github.com/porter-dev/porter/cli/cmd"
  14. "github.com/porter-dev/porter/cli/cmd/api"
  15. "github.com/spf13/viper"
  16. "k8s.io/client-go/util/homedir"
  17. )
  18. // PorterHelper implements credentials.Helper: it acts as a credentials
  19. // helper for Docker that allows authentication with different registries.
  20. type PorterHelper struct {
  21. Debug bool
  22. credCache CredentialsCache
  23. }
  24. // Add appends credentials to the store.
  25. func (p *PorterHelper) Add(cr *credentials.Credentials) error {
  26. // Doesn't seem to be called
  27. return nil
  28. }
  29. // Delete removes credentials from the store.
  30. func (p *PorterHelper) Delete(serverURL string) error {
  31. // Doesn't seem to be called
  32. return nil
  33. }
  34. 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)?`)
  35. // Get retrieves credentials from the store.
  36. // It returns username and secret as strings.
  37. func (p *PorterHelper) Get(serverURL string) (user string, secret string, err error) {
  38. // parse the server url for region
  39. matches := ecrPattern.FindStringSubmatch(serverURL)
  40. if len(matches) == 0 {
  41. err := fmt.Errorf("only ECR registry URLs are supported")
  42. if p.Debug {
  43. log.Printf("Error: %s\n", err.Error())
  44. }
  45. return "", "", err
  46. } else if len(matches) < 3 {
  47. err := fmt.Errorf("%s is not a valid ECR repository URI", serverURL)
  48. if p.Debug {
  49. log.Printf("Error: %s\n", err.Error())
  50. }
  51. return "", "", err
  52. }
  53. region := matches[3]
  54. credCache := BuildCredentialsCache(region)
  55. cachedEntry := credCache.Get(serverURL)
  56. var token string
  57. if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
  58. token = cachedEntry.AuthorizationToken
  59. } else {
  60. host := viper.GetString("host")
  61. projID := viper.GetUint("project")
  62. client := api.NewClient(host+"/api", "cookie.json")
  63. // get a token from the server
  64. tokenResp, err := client.GetECRAuthorizationToken(context.Background(), projID, matches[3])
  65. if err != nil {
  66. return "", "", err
  67. }
  68. token = tokenResp.Token
  69. // set the token in cache
  70. credCache.Set(serverURL, &AuthEntry{
  71. AuthorizationToken: token,
  72. RequestedAt: time.Now(),
  73. ExpiresAt: *tokenResp.ExpiresAt,
  74. ProxyEndpoint: serverURL,
  75. })
  76. }
  77. return p.getAuth(token)
  78. }
  79. // List returns the stored serverURLs and their associated usernames.
  80. func (p *PorterHelper) List() (map[string]string, error) {
  81. credCache := BuildCredentialsCache("")
  82. entries := credCache.List()
  83. res := make(map[string]string)
  84. for _, entry := range entries {
  85. user, _, err := p.getAuth(entry.AuthorizationToken)
  86. if err != nil {
  87. continue
  88. }
  89. res[entry.ProxyEndpoint] = user
  90. }
  91. return res, nil
  92. }
  93. func (p *PorterHelper) getAuth(token string) (string, string, error) {
  94. decodedToken, err := base64.StdEncoding.DecodeString(token)
  95. if err != nil {
  96. return "", "", fmt.Errorf("Invalid token: %v", err)
  97. }
  98. parts := strings.SplitN(string(decodedToken), ":", 2)
  99. if len(parts) < 2 {
  100. return "", "", fmt.Errorf("Invalid token: expected two parts, got %d", len(parts))
  101. }
  102. return parts[0], parts[1], nil
  103. }
  104. func (p *PorterHelper) init() {
  105. cmd.Setup()
  106. if p.Debug {
  107. var home = homedir.HomeDir()
  108. file, err := os.OpenFile(filepath.Join(home, ".porter", "logs.txt"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
  109. if err == nil {
  110. log.SetOutput(file)
  111. }
  112. }
  113. }