helper.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. package helper
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "fmt"
  6. "log"
  7. "net/url"
  8. "os"
  9. "path/filepath"
  10. "regexp"
  11. "strings"
  12. "time"
  13. "github.com/docker/docker-credential-helpers/credentials"
  14. "github.com/porter-dev/porter/cli/cmd"
  15. "github.com/porter-dev/porter/cli/cmd/api"
  16. "github.com/spf13/viper"
  17. "k8s.io/client-go/util/homedir"
  18. )
  19. // PorterHelper implements credentials.Helper: it acts as a credentials
  20. // helper for Docker that allows authentication with different registries.
  21. type PorterHelper struct {
  22. Debug bool
  23. credCache CredentialsCache
  24. }
  25. // Add appends credentials to the store.
  26. func (p *PorterHelper) Add(cr *credentials.Credentials) error {
  27. // Doesn't seem to be called
  28. return nil
  29. }
  30. // Delete removes credentials from the store.
  31. func (p *PorterHelper) Delete(serverURL string) error {
  32. // Doesn't seem to be called
  33. return nil
  34. }
  35. 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)?`)
  36. // Get retrieves credentials from the store.
  37. // It returns username and secret as strings.
  38. func (p *PorterHelper) Get(serverURL string) (user string, secret string, err error) {
  39. p.init()
  40. if strings.Contains(serverURL, "gcr.io") {
  41. return p.getGCR(serverURL)
  42. } else if strings.Contains(serverURL, "registry.digitalocean.com") {
  43. return p.getDOCR(serverURL)
  44. }
  45. return p.getECR(serverURL)
  46. }
  47. func (p *PorterHelper) getGCR(serverURL string) (user string, secret string, err error) {
  48. urlP, err := url.Parse("https://" + serverURL)
  49. if err != nil {
  50. return "", "", err
  51. }
  52. credCache := BuildCredentialsCache(urlP.Host)
  53. cachedEntry := credCache.Get(serverURL)
  54. var token string
  55. if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
  56. token = cachedEntry.AuthorizationToken
  57. } else {
  58. projID := viper.GetUint("project")
  59. client := cmd.GetAPIClient()
  60. // get a token from the server
  61. tokenResp, err := client.GetGCRAuthorizationToken(context.Background(), projID, &api.GetGCRTokenRequest{
  62. ServerURL: serverURL,
  63. })
  64. if err != nil {
  65. return "", "", err
  66. }
  67. token = tokenResp.Token
  68. // set the token in cache
  69. credCache.Set(serverURL, &AuthEntry{
  70. AuthorizationToken: token,
  71. RequestedAt: time.Now(),
  72. ExpiresAt: *tokenResp.ExpiresAt,
  73. ProxyEndpoint: serverURL,
  74. })
  75. }
  76. return "oauth2accesstoken", token, nil
  77. }
  78. func (p *PorterHelper) getDOCR(serverURL string) (user string, secret string, err error) {
  79. urlP, err := url.Parse("https://" + serverURL)
  80. if err != nil {
  81. if p.Debug {
  82. log.Printf("Error: %s\n", err.Error())
  83. }
  84. return "", "", err
  85. }
  86. credCache := BuildCredentialsCache(urlP.Host)
  87. cachedEntry := credCache.Get(serverURL)
  88. var token string
  89. if p.Debug {
  90. log.Printf("GETTING FROM DOCR", urlP)
  91. }
  92. if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
  93. token = cachedEntry.AuthorizationToken
  94. if p.Debug {
  95. log.Printf("USING CACHED TOKEN", token)
  96. }
  97. } else {
  98. host := viper.GetString("host")
  99. projID := viper.GetUint("project")
  100. client := cmd.GetAPIClient()
  101. if p.Debug {
  102. log.Printf("MAKING REQUEST", host, projID)
  103. }
  104. // get a token from the server
  105. tokenResp, err := client.GetDOCRAuthorizationToken(context.Background(), projID, &api.GetDOCRTokenRequest{
  106. ServerURL: serverURL,
  107. })
  108. if err != nil {
  109. if p.Debug {
  110. log.Printf("Error: %s\n", err.Error())
  111. }
  112. return "", "", err
  113. }
  114. token = tokenResp.Token
  115. if t := *tokenResp.ExpiresAt; len(token) > 0 && !t.IsZero() {
  116. // set the token in cache
  117. credCache.Set(serverURL, &AuthEntry{
  118. AuthorizationToken: token,
  119. RequestedAt: time.Now(),
  120. ExpiresAt: t,
  121. ProxyEndpoint: serverURL,
  122. })
  123. }
  124. }
  125. return token, token, nil
  126. }
  127. func (p *PorterHelper) getECR(serverURL string) (user string, secret string, err error) {
  128. // parse the server url for region
  129. matches := ecrPattern.FindStringSubmatch(serverURL)
  130. if len(matches) == 0 {
  131. err := fmt.Errorf("only ECR registry URLs are supported")
  132. if p.Debug {
  133. log.Printf("Error: %s\n", err.Error())
  134. }
  135. return "", "", err
  136. } else if len(matches) < 3 {
  137. err := fmt.Errorf("%s is not a valid ECR repository URI", serverURL)
  138. if p.Debug {
  139. log.Printf("Error: %s\n", err.Error())
  140. }
  141. return "", "", err
  142. }
  143. region := matches[3]
  144. credCache := BuildCredentialsCache(region)
  145. cachedEntry := credCache.Get(serverURL)
  146. var token string
  147. if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
  148. token = cachedEntry.AuthorizationToken
  149. } else {
  150. projID := viper.GetUint("project")
  151. client := cmd.GetAPIClient()
  152. // get a token from the server
  153. tokenResp, err := client.GetECRAuthorizationToken(context.Background(), projID, matches[3])
  154. if err != nil {
  155. return "", "", err
  156. }
  157. token = tokenResp.Token
  158. // set the token in cache
  159. credCache.Set(serverURL, &AuthEntry{
  160. AuthorizationToken: token,
  161. RequestedAt: time.Now(),
  162. ExpiresAt: *tokenResp.ExpiresAt,
  163. ProxyEndpoint: serverURL,
  164. })
  165. }
  166. return p.getAuth(token)
  167. }
  168. // List returns the stored serverURLs and their associated usernames.
  169. func (p *PorterHelper) List() (map[string]string, error) {
  170. p.init()
  171. credCache := BuildCredentialsCache("")
  172. entries := credCache.List()
  173. res := make(map[string]string)
  174. for _, entry := range entries {
  175. user, _, err := p.getAuth(entry.AuthorizationToken)
  176. if err != nil {
  177. continue
  178. }
  179. res[entry.ProxyEndpoint] = user
  180. }
  181. return res, nil
  182. }
  183. func (p *PorterHelper) getAuth(token string) (string, string, error) {
  184. decodedToken, err := base64.StdEncoding.DecodeString(token)
  185. if err != nil {
  186. return "", "", fmt.Errorf("Invalid token: %v", err)
  187. }
  188. parts := strings.SplitN(string(decodedToken), ":", 2)
  189. if len(parts) < 2 {
  190. return "", "", fmt.Errorf("Invalid token: expected two parts, got %d", len(parts))
  191. }
  192. return parts[0], parts[1], nil
  193. }
  194. func (p *PorterHelper) init() {
  195. cmd.Setup()
  196. if p.Debug {
  197. var home = homedir.HomeDir()
  198. file, err := os.OpenFile(filepath.Join(home, ".porter", "logs.txt"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
  199. if err == nil {
  200. log.SetOutput(file)
  201. }
  202. }
  203. }