helper.go 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  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. host := viper.GetString("host")
  59. projID := viper.GetUint("project")
  60. client := cmd.GetAPIClient()
  61. // get a token from the server
  62. tokenResp, err := client.GetGCRAuthorizationToken(context.Background(), projID, &api.GetGCRTokenRequest{
  63. ServerURL: serverURL,
  64. })
  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 "oauth2accesstoken", token, nil
  78. }
  79. func (p *PorterHelper) getDOCR(serverURL string) (user string, secret string, err error) {
  80. urlP, err := url.Parse("https://" + serverURL)
  81. if err != nil {
  82. if p.Debug {
  83. log.Printf("Error: %s\n", err.Error())
  84. }
  85. return "", "", err
  86. }
  87. credCache := BuildCredentialsCache(urlP.Host)
  88. cachedEntry := credCache.Get(serverURL)
  89. var token string
  90. if p.Debug {
  91. log.Printf("GETTING FROM DOCR", urlP)
  92. }
  93. if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
  94. token = cachedEntry.AuthorizationToken
  95. if p.Debug {
  96. log.Printf("USING CACHED TOKEN", token)
  97. }
  98. } else {
  99. host := viper.GetString("host")
  100. projID := viper.GetUint("project")
  101. client := cmd.GetAPIClient()
  102. if p.Debug {
  103. log.Printf("MAKING REQUEST", host, projID)
  104. }
  105. // get a token from the server
  106. tokenResp, err := client.GetDOCRAuthorizationToken(context.Background(), projID, &api.GetDOCRTokenRequest{
  107. ServerURL: serverURL,
  108. })
  109. if err != nil {
  110. if p.Debug {
  111. log.Printf("Error: %s\n", err.Error())
  112. }
  113. return "", "", err
  114. }
  115. token = tokenResp.Token
  116. if t := *tokenResp.ExpiresAt; len(token) > 0 && !t.IsZero() {
  117. // set the token in cache
  118. credCache.Set(serverURL, &AuthEntry{
  119. AuthorizationToken: token,
  120. RequestedAt: time.Now(),
  121. ExpiresAt: t,
  122. ProxyEndpoint: serverURL,
  123. })
  124. }
  125. }
  126. return token, token, nil
  127. }
  128. func (p *PorterHelper) getECR(serverURL string) (user string, secret string, err error) {
  129. // parse the server url for region
  130. matches := ecrPattern.FindStringSubmatch(serverURL)
  131. if len(matches) == 0 {
  132. err := fmt.Errorf("only ECR registry URLs are supported")
  133. if p.Debug {
  134. log.Printf("Error: %s\n", err.Error())
  135. }
  136. return "", "", err
  137. } else if len(matches) < 3 {
  138. err := fmt.Errorf("%s is not a valid ECR repository URI", serverURL)
  139. if p.Debug {
  140. log.Printf("Error: %s\n", err.Error())
  141. }
  142. return "", "", err
  143. }
  144. region := matches[3]
  145. credCache := BuildCredentialsCache(region)
  146. cachedEntry := credCache.Get(serverURL)
  147. var token string
  148. if cachedEntry != nil && cachedEntry.IsValid(time.Now()) {
  149. token = cachedEntry.AuthorizationToken
  150. } else {
  151. host := viper.GetString("host")
  152. projID := viper.GetUint("project")
  153. client := cmd.GetAPIClient()
  154. // get a token from the server
  155. tokenResp, err := client.GetECRAuthorizationToken(context.Background(), projID, matches[3])
  156. if err != nil {
  157. return "", "", err
  158. }
  159. token = tokenResp.Token
  160. // set the token in cache
  161. credCache.Set(serverURL, &AuthEntry{
  162. AuthorizationToken: token,
  163. RequestedAt: time.Now(),
  164. ExpiresAt: *tokenResp.ExpiresAt,
  165. ProxyEndpoint: serverURL,
  166. })
  167. }
  168. return p.getAuth(token)
  169. }
  170. // List returns the stored serverURLs and their associated usernames.
  171. func (p *PorterHelper) List() (map[string]string, error) {
  172. p.init()
  173. credCache := BuildCredentialsCache("")
  174. entries := credCache.List()
  175. res := make(map[string]string)
  176. for _, entry := range entries {
  177. user, _, err := p.getAuth(entry.AuthorizationToken)
  178. if err != nil {
  179. continue
  180. }
  181. res[entry.ProxyEndpoint] = user
  182. }
  183. return res, nil
  184. }
  185. func (p *PorterHelper) getAuth(token string) (string, string, error) {
  186. decodedToken, err := base64.StdEncoding.DecodeString(token)
  187. if err != nil {
  188. return "", "", fmt.Errorf("Invalid token: %v", err)
  189. }
  190. parts := strings.SplitN(string(decodedToken), ":", 2)
  191. if len(parts) < 2 {
  192. return "", "", fmt.Errorf("Invalid token: expected two parts, got %d", len(parts))
  193. }
  194. return parts[0], parts[1], nil
  195. }
  196. func (p *PorterHelper) init() {
  197. cmd.Setup()
  198. if p.Debug {
  199. var home = homedir.HomeDir()
  200. file, err := os.OpenFile(filepath.Join(home, ".porter", "logs.txt"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
  201. if err == nil {
  202. log.SetOutput(file)
  203. }
  204. }
  205. }