registry.go 26 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283
  1. package registry
  2. import (
  3. "context"
  4. "encoding/base64"
  5. "encoding/json"
  6. "fmt"
  7. "net/http"
  8. "net/url"
  9. "strings"
  10. "time"
  11. "github.com/Azure/azure-sdk-for-go/sdk/azidentity"
  12. "github.com/aws/aws-sdk-go/aws/awserr"
  13. "github.com/aws/aws-sdk-go/service/ecr"
  14. "github.com/porter-dev/porter/internal/models"
  15. "github.com/porter-dev/porter/internal/oauth"
  16. "github.com/porter-dev/porter/internal/repository"
  17. "golang.org/x/oauth2"
  18. ints "github.com/porter-dev/porter/internal/models/integrations"
  19. ptypes "github.com/porter-dev/porter/api/types"
  20. "github.com/digitalocean/godo"
  21. "github.com/docker/cli/cli/config/configfile"
  22. "github.com/docker/cli/cli/config/types"
  23. "github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/containerregistry/armcontainerregistry"
  24. "github.com/Azure/azure-sdk-for-go/sdk/azcore/to"
  25. )
  26. // Registry wraps the gorm Registry model
  27. type Registry models.Registry
  28. func GetECRRegistryURL(awsIntRepo repository.AWSIntegrationRepository, projectID, awsIntID uint) (string, error) {
  29. awsInt, err := awsIntRepo.ReadAWSIntegration(projectID, awsIntID)
  30. if err != nil {
  31. return "", err
  32. }
  33. sess, err := awsInt.GetSession()
  34. if err != nil {
  35. return "", err
  36. }
  37. ecrSvc := ecr.New(sess)
  38. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  39. if err != nil {
  40. return "", err
  41. }
  42. return *output.AuthorizationData[0].ProxyEndpoint, nil
  43. }
  44. // ListRepositories lists the repositories for a registry
  45. func (r *Registry) ListRepositories(
  46. repo repository.Repository,
  47. doAuth *oauth2.Config, // only required if using DOCR
  48. ) ([]*ptypes.RegistryRepository, error) {
  49. // switch on the auth mechanism to get a token
  50. if r.AWSIntegrationID != 0 {
  51. return r.listECRRepositories(repo)
  52. }
  53. if r.GCPIntegrationID != 0 {
  54. return r.listGCRRepositories(repo)
  55. }
  56. if r.DOIntegrationID != 0 {
  57. return r.listDOCRRepositories(repo, doAuth)
  58. }
  59. if r.AzureIntegrationID != 0 {
  60. return r.listACRRepositories(repo)
  61. }
  62. if r.BasicIntegrationID != 0 {
  63. return r.listPrivateRegistryRepositories(repo)
  64. }
  65. return nil, fmt.Errorf("error listing repositories")
  66. }
  67. type gcrJWT struct {
  68. AccessToken string `json:"token"`
  69. ExpiresInSec int `json:"expires_in"`
  70. }
  71. type gcrErr struct {
  72. Code string `json:"code"`
  73. Message string `json:"message"`
  74. }
  75. type gcrRepositoryResp struct {
  76. Repositories []string `json:"repositories"`
  77. Errors []gcrErr `json:"errors"`
  78. }
  79. func (r *Registry) GetGCRToken(repo repository.Repository) (*oauth2.Token, error) {
  80. getTokenCache := r.getTokenCacheFunc(repo)
  81. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  82. r.ProjectID,
  83. r.GCPIntegrationID,
  84. )
  85. if err != nil {
  86. return nil, err
  87. }
  88. // get oauth2 access token
  89. return gcp.GetBearerToken(
  90. getTokenCache,
  91. r.setTokenCacheFunc(repo),
  92. "https://www.googleapis.com/auth/devstorage.read_write",
  93. )
  94. }
  95. func (r *Registry) listGCRRepositories(
  96. repo repository.Repository,
  97. ) ([]*ptypes.RegistryRepository, error) {
  98. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  99. r.ProjectID,
  100. r.GCPIntegrationID,
  101. )
  102. if err != nil {
  103. return nil, err
  104. }
  105. // Just use service account key to authenticate, since scopes may not be in place
  106. // for oauth. This also prevents us from making more requests.
  107. client := &http.Client{}
  108. regURL := r.URL
  109. if !strings.HasPrefix(regURL, "http") {
  110. regURL = fmt.Sprintf("https://%s", regURL)
  111. }
  112. regURLParsed, err := url.Parse(regURL)
  113. regHostname := "gcr.io"
  114. if err == nil {
  115. regHostname = regURLParsed.Host
  116. }
  117. req, err := http.NewRequest(
  118. "GET",
  119. fmt.Sprintf("https://%s/v2/_catalog", regHostname),
  120. nil,
  121. )
  122. if err != nil {
  123. return nil, err
  124. }
  125. req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))
  126. resp, err := client.Do(req)
  127. if err != nil {
  128. return nil, err
  129. }
  130. gcrResp := gcrRepositoryResp{}
  131. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  132. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  133. }
  134. if len(gcrResp.Errors) > 0 {
  135. errMsg := ""
  136. for _, gcrErr := range gcrResp.Errors {
  137. errMsg += fmt.Sprintf(": Code %s, message %s", gcrErr.Code, gcrErr.Message)
  138. }
  139. return nil, fmt.Errorf(errMsg)
  140. }
  141. res := make([]*ptypes.RegistryRepository, 0)
  142. parsedURL, err := url.Parse("https://" + r.URL)
  143. if err != nil {
  144. return nil, err
  145. }
  146. for _, repo := range gcrResp.Repositories {
  147. res = append(res, &ptypes.RegistryRepository{
  148. Name: repo,
  149. URI: parsedURL.Host + "/" + repo,
  150. })
  151. }
  152. return res, nil
  153. }
  154. func (r *Registry) listECRRepositories(repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
  155. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  156. r.ProjectID,
  157. r.AWSIntegrationID,
  158. )
  159. if err != nil {
  160. return nil, err
  161. }
  162. sess, err := aws.GetSession()
  163. if err != nil {
  164. return nil, err
  165. }
  166. svc := ecr.New(sess)
  167. resp, err := svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{})
  168. if err != nil {
  169. return nil, err
  170. }
  171. res := make([]*ptypes.RegistryRepository, 0)
  172. for _, repo := range resp.Repositories {
  173. res = append(res, &ptypes.RegistryRepository{
  174. Name: *repo.RepositoryName,
  175. CreatedAt: *repo.CreatedAt,
  176. URI: *repo.RepositoryUri,
  177. })
  178. }
  179. return res, nil
  180. }
  181. func (r *Registry) listACRRepositories(repo repository.Repository) ([]*ptypes.RegistryRepository, error) {
  182. az, err := repo.AzureIntegration().ReadAzureIntegration(
  183. r.ProjectID,
  184. r.AzureIntegrationID,
  185. )
  186. if err != nil {
  187. return nil, err
  188. }
  189. client := &http.Client{}
  190. req, err := http.NewRequest(
  191. "GET",
  192. fmt.Sprintf("%s/v2/_catalog", r.URL),
  193. nil,
  194. )
  195. if err != nil {
  196. return nil, err
  197. }
  198. req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))
  199. resp, err := client.Do(req)
  200. if err != nil {
  201. return nil, err
  202. }
  203. gcrResp := gcrRepositoryResp{}
  204. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  205. return nil, fmt.Errorf("Could not read Azure registry repositories: %v", err)
  206. }
  207. res := make([]*ptypes.RegistryRepository, 0)
  208. if err != nil {
  209. return nil, err
  210. }
  211. for _, repo := range gcrResp.Repositories {
  212. res = append(res, &ptypes.RegistryRepository{
  213. Name: repo,
  214. URI: strings.TrimPrefix(r.URL, "https://") + "/" + repo,
  215. })
  216. }
  217. return res, nil
  218. }
  219. // Returns the username/password pair for the registry
  220. func (r *Registry) GetACRCredentials(repo repository.Repository) (string, string, error) {
  221. az, err := repo.AzureIntegration().ReadAzureIntegration(
  222. r.ProjectID,
  223. r.AzureIntegrationID,
  224. )
  225. if err != nil {
  226. return "", "", err
  227. }
  228. // if the passwords and name aren't set, generate them
  229. if az.ACRTokenName == "" || len(az.ACRPassword1) == 0 {
  230. az.ACRTokenName = "porter-acr-token"
  231. // create an acr repo token
  232. cred, err := azidentity.NewClientSecretCredential(az.AzureTenantID, az.AzureClientID, string(az.ServicePrincipalSecret), nil)
  233. if err != nil {
  234. return "", "", err
  235. }
  236. scopeMapsClient, err := armcontainerregistry.NewScopeMapsClient(az.AzureSubscriptionID, cred, nil)
  237. if err != nil {
  238. return "", "", err
  239. }
  240. smRes, err := scopeMapsClient.Get(
  241. context.Background(),
  242. az.ACRResourceGroupName,
  243. az.ACRName,
  244. "_repositories_admin",
  245. nil,
  246. )
  247. if err != nil {
  248. return "", "", err
  249. }
  250. tokensClient, err := armcontainerregistry.NewTokensClient(az.AzureSubscriptionID, cred, nil)
  251. if err != nil {
  252. return "", "", err
  253. }
  254. pollerResp, err := tokensClient.BeginCreate(
  255. context.Background(),
  256. az.ACRResourceGroupName,
  257. az.ACRName,
  258. "porter-acr-token",
  259. armcontainerregistry.Token{
  260. Properties: &armcontainerregistry.TokenProperties{
  261. ScopeMapID: smRes.ID,
  262. Status: to.Ptr(armcontainerregistry.TokenStatusEnabled),
  263. },
  264. },
  265. nil,
  266. )
  267. if err != nil {
  268. return "", "", err
  269. }
  270. tokResp, err := pollerResp.PollUntilDone(context.Background(), 2*time.Second)
  271. if err != nil {
  272. return "", "", err
  273. }
  274. // TODO: CALL GENERATE CREDENTIALS ENDPOINT
  275. registriesClient, err := armcontainerregistry.NewRegistriesClient(az.AzureSubscriptionID, cred, nil)
  276. if err != nil {
  277. return "", "", err
  278. }
  279. poller, err := registriesClient.BeginGenerateCredentials(
  280. context.Background(),
  281. az.ACRResourceGroupName,
  282. az.ACRName,
  283. armcontainerregistry.GenerateCredentialsParameters{
  284. TokenID: tokResp.ID,
  285. },
  286. &armcontainerregistry.RegistriesClientBeginGenerateCredentialsOptions{ResumeToken: ""})
  287. if err != nil {
  288. return "", "", err
  289. }
  290. genCredentialsResp, err := poller.PollUntilDone(context.Background(), 2*time.Second)
  291. if err != nil {
  292. return "", "", err
  293. }
  294. for i, tokPassword := range genCredentialsResp.Passwords {
  295. if i == 0 {
  296. az.ACRPassword1 = []byte(*tokPassword.Value)
  297. } else if i == 1 {
  298. az.ACRPassword2 = []byte(*tokPassword.Value)
  299. }
  300. }
  301. // update the az integration
  302. az, err = repo.AzureIntegration().OverwriteAzureIntegration(
  303. az,
  304. )
  305. if err != nil {
  306. return "", "", err
  307. }
  308. }
  309. return az.ACRTokenName, string(az.ACRPassword1), nil
  310. }
  311. func (r *Registry) listDOCRRepositories(
  312. repo repository.Repository,
  313. doAuth *oauth2.Config,
  314. ) ([]*ptypes.RegistryRepository, error) {
  315. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  316. r.ProjectID,
  317. r.DOIntegrationID,
  318. )
  319. if err != nil {
  320. return nil, err
  321. }
  322. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  323. if err != nil {
  324. return nil, err
  325. }
  326. client := godo.NewFromToken(tok)
  327. urlArr := strings.Split(r.URL, "/")
  328. if len(urlArr) != 2 {
  329. return nil, fmt.Errorf("invalid digital ocean registry url")
  330. }
  331. name := urlArr[1]
  332. repos, _, err := client.Registry.ListRepositories(context.TODO(), name, &godo.ListOptions{})
  333. if err != nil {
  334. return nil, err
  335. }
  336. res := make([]*ptypes.RegistryRepository, 0)
  337. for _, repo := range repos {
  338. res = append(res, &ptypes.RegistryRepository{
  339. Name: repo.Name,
  340. URI: r.URL + "/" + repo.Name,
  341. })
  342. }
  343. return res, nil
  344. }
  345. func (r *Registry) listPrivateRegistryRepositories(
  346. repo repository.Repository,
  347. ) ([]*ptypes.RegistryRepository, error) {
  348. // handle dockerhub different, as it doesn't implement the docker registry http api
  349. if strings.Contains(r.URL, "docker.io") {
  350. // in this case, we just return the single dockerhub repository that's linked
  351. res := make([]*ptypes.RegistryRepository, 0)
  352. res = append(res, &ptypes.RegistryRepository{
  353. Name: strings.Split(r.URL, "docker.io/")[1],
  354. URI: r.URL,
  355. })
  356. return res, nil
  357. }
  358. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  359. r.ProjectID,
  360. r.BasicIntegrationID,
  361. )
  362. if err != nil {
  363. return nil, err
  364. }
  365. // Just use service account key to authenticate, since scopes may not be in place
  366. // for oauth. This also prevents us from making more requests.
  367. client := &http.Client{}
  368. // get the host and scheme to make the request
  369. parsedURL, err := url.Parse(r.URL)
  370. req, err := http.NewRequest(
  371. "GET",
  372. fmt.Sprintf("%s://%s/v2/_catalog", parsedURL.Scheme, parsedURL.Host),
  373. nil,
  374. )
  375. if err != nil {
  376. return nil, err
  377. }
  378. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  379. resp, err := client.Do(req)
  380. if err != nil {
  381. return nil, err
  382. }
  383. // if the status code is 404, fallback to the Docker Hub implementation
  384. if resp.StatusCode == 404 {
  385. req, err := http.NewRequest(
  386. "GET",
  387. fmt.Sprintf("%s/", r.URL),
  388. nil,
  389. )
  390. if err != nil {
  391. return nil, err
  392. }
  393. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  394. resp, err = client.Do(req)
  395. if err != nil {
  396. return nil, err
  397. }
  398. }
  399. gcrResp := gcrRepositoryResp{}
  400. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  401. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  402. }
  403. res := make([]*ptypes.RegistryRepository, 0)
  404. if err != nil {
  405. return nil, err
  406. }
  407. for _, repo := range gcrResp.Repositories {
  408. res = append(res, &ptypes.RegistryRepository{
  409. Name: repo,
  410. URI: parsedURL.Host + "/" + repo,
  411. })
  412. }
  413. return res, nil
  414. }
  415. func (r *Registry) getTokenCacheFunc(
  416. repo repository.Repository,
  417. ) ints.GetTokenCacheFunc {
  418. return func() (tok *ints.TokenCache, err error) {
  419. reg, err := repo.Registry().ReadRegistry(r.ProjectID, r.ID)
  420. if err != nil {
  421. return nil, err
  422. }
  423. return &reg.TokenCache.TokenCache, nil
  424. }
  425. }
  426. func (r *Registry) setTokenCacheFunc(
  427. repo repository.Repository,
  428. ) ints.SetTokenCacheFunc {
  429. return func(token string, expiry time.Time) error {
  430. _, err := repo.Registry().UpdateRegistryTokenCache(
  431. &ints.RegTokenCache{
  432. TokenCache: ints.TokenCache{
  433. Token: []byte(token),
  434. Expiry: expiry,
  435. },
  436. RegistryID: r.ID,
  437. },
  438. )
  439. return err
  440. }
  441. }
  442. // CreateRepository creates a repository for a registry, if needed
  443. // (currently only required for ECR)
  444. func (r *Registry) CreateRepository(
  445. repo repository.Repository,
  446. name string,
  447. ) error {
  448. // if aws, create repository
  449. if r.AWSIntegrationID != 0 {
  450. return r.createECRRepository(repo, name)
  451. }
  452. // otherwise, no-op
  453. return nil
  454. }
  455. func (r *Registry) createECRRepository(
  456. repo repository.Repository,
  457. name string,
  458. ) error {
  459. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  460. r.ProjectID,
  461. r.AWSIntegrationID,
  462. )
  463. if err != nil {
  464. return err
  465. }
  466. sess, err := aws.GetSession()
  467. if err != nil {
  468. return err
  469. }
  470. svc := ecr.New(sess)
  471. // determine if repository already exists
  472. _, err = svc.DescribeRepositories(&ecr.DescribeRepositoriesInput{
  473. RepositoryNames: []*string{&name},
  474. })
  475. // if the repository was not found, create it
  476. if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryNotFoundException {
  477. _, err = svc.CreateRepository(&ecr.CreateRepositoryInput{
  478. RepositoryName: &name,
  479. })
  480. return err
  481. } else if err != nil {
  482. return err
  483. }
  484. return nil
  485. }
  486. // ListImages lists the images for an image repository
  487. func (r *Registry) ListImages(
  488. repoName string,
  489. repo repository.Repository,
  490. doAuth *oauth2.Config, // only required if using DOCR
  491. ) ([]*ptypes.Image, error) {
  492. // switch on the auth mechanism to get a token
  493. if r.AWSIntegrationID != 0 {
  494. return r.listECRImages(repoName, repo)
  495. }
  496. if r.AzureIntegrationID != 0 {
  497. return r.listACRImages(repoName, repo)
  498. }
  499. if r.GCPIntegrationID != 0 {
  500. return r.listGCRImages(repoName, repo)
  501. }
  502. if r.DOIntegrationID != 0 {
  503. return r.listDOCRImages(repoName, repo, doAuth)
  504. }
  505. if r.BasicIntegrationID != 0 {
  506. return r.listPrivateRegistryImages(repoName, repo)
  507. }
  508. return nil, fmt.Errorf("error listing images")
  509. }
  510. func (r *Registry) listECRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  511. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  512. r.ProjectID,
  513. r.AWSIntegrationID,
  514. )
  515. if err != nil {
  516. return nil, err
  517. }
  518. sess, err := aws.GetSession()
  519. if err != nil {
  520. return nil, err
  521. }
  522. svc := ecr.New(sess)
  523. resp, err := svc.ListImages(&ecr.ListImagesInput{
  524. RepositoryName: &repoName,
  525. })
  526. if err != nil {
  527. return nil, err
  528. }
  529. describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
  530. RepositoryName: &repoName,
  531. ImageIds: resp.ImageIds,
  532. })
  533. if err != nil {
  534. return nil, err
  535. }
  536. imageDetails := describeResp.ImageDetails
  537. nextToken := describeResp.NextToken
  538. for nextToken != nil {
  539. describeResp, err := svc.DescribeImages(&ecr.DescribeImagesInput{
  540. RepositoryName: &repoName,
  541. ImageIds: resp.ImageIds,
  542. })
  543. if err != nil {
  544. return nil, err
  545. }
  546. nextToken = describeResp.NextToken
  547. imageDetails = append(imageDetails, describeResp.ImageDetails...)
  548. }
  549. res := make([]*ptypes.Image, 0)
  550. for _, img := range imageDetails {
  551. for _, tag := range img.ImageTags {
  552. res = append(res, &ptypes.Image{
  553. Digest: *img.ImageDigest,
  554. Tag: *tag,
  555. RepositoryName: repoName,
  556. PushedAt: img.ImagePushedAt,
  557. })
  558. }
  559. }
  560. return res, nil
  561. }
  562. func (r *Registry) listACRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  563. az, err := repo.AzureIntegration().ReadAzureIntegration(
  564. r.ProjectID,
  565. r.AzureIntegrationID,
  566. )
  567. if err != nil {
  568. return nil, err
  569. }
  570. // use JWT token to request catalog
  571. client := &http.Client{}
  572. req, err := http.NewRequest(
  573. "GET",
  574. fmt.Sprintf("%s/v2/%s/tags/list", r.URL, repoName),
  575. nil,
  576. )
  577. if err != nil {
  578. return nil, err
  579. }
  580. req.SetBasicAuth(az.AzureClientID, string(az.ServicePrincipalSecret))
  581. resp, err := client.Do(req)
  582. if err != nil {
  583. return nil, err
  584. }
  585. gcrResp := gcrImageResp{}
  586. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  587. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  588. }
  589. res := make([]*ptypes.Image, 0)
  590. for _, tag := range gcrResp.Tags {
  591. res = append(res, &ptypes.Image{
  592. RepositoryName: strings.TrimPrefix(repoName, "https://"),
  593. Tag: tag,
  594. })
  595. }
  596. return res, nil
  597. }
  598. type gcrImageResp struct {
  599. Tags []string `json:"tags"`
  600. }
  601. func (r *Registry) listGCRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  602. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  603. r.ProjectID,
  604. r.GCPIntegrationID,
  605. )
  606. if err != nil {
  607. return nil, err
  608. }
  609. // use JWT token to request catalog
  610. client := &http.Client{}
  611. parsedURL, err := url.Parse("https://" + r.URL)
  612. if err != nil {
  613. return nil, err
  614. }
  615. trimmedPath := strings.Trim(parsedURL.Path, "/")
  616. req, err := http.NewRequest(
  617. "GET",
  618. fmt.Sprintf("https://%s/v2/%s/%s/tags/list", parsedURL.Host, trimmedPath, repoName),
  619. nil,
  620. )
  621. if err != nil {
  622. return nil, err
  623. }
  624. req.SetBasicAuth("_json_key", string(gcp.GCPKeyData))
  625. resp, err := client.Do(req)
  626. if err != nil {
  627. return nil, err
  628. }
  629. gcrResp := gcrImageResp{}
  630. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  631. return nil, fmt.Errorf("Could not read GCR repositories: %v", err)
  632. }
  633. res := make([]*ptypes.Image, 0)
  634. for _, tag := range gcrResp.Tags {
  635. res = append(res, &ptypes.Image{
  636. RepositoryName: repoName,
  637. Tag: tag,
  638. })
  639. }
  640. return res, nil
  641. }
  642. func (r *Registry) listDOCRImages(
  643. repoName string,
  644. repo repository.Repository,
  645. doAuth *oauth2.Config,
  646. ) ([]*ptypes.Image, error) {
  647. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  648. r.ProjectID,
  649. r.DOIntegrationID,
  650. )
  651. if err != nil {
  652. return nil, err
  653. }
  654. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  655. if err != nil {
  656. return nil, err
  657. }
  658. client := godo.NewFromToken(tok)
  659. urlArr := strings.Split(r.URL, "/")
  660. if len(urlArr) != 2 {
  661. return nil, fmt.Errorf("invalid digital ocean registry url")
  662. }
  663. name := urlArr[1]
  664. tags, _, err := client.Registry.ListRepositoryTags(context.TODO(), name, repoName, &godo.ListOptions{})
  665. if err != nil {
  666. return nil, err
  667. }
  668. res := make([]*ptypes.Image, 0)
  669. for _, tag := range tags {
  670. res = append(res, &ptypes.Image{
  671. RepositoryName: repoName,
  672. Tag: tag.Tag,
  673. })
  674. }
  675. return res, nil
  676. }
  677. func (r *Registry) listPrivateRegistryImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  678. // handle dockerhub different, as it doesn't implement the docker registry http api
  679. if strings.Contains(r.URL, "docker.io") {
  680. return r.listDockerHubImages(repoName, repo)
  681. }
  682. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  683. r.ProjectID,
  684. r.BasicIntegrationID,
  685. )
  686. if err != nil {
  687. return nil, err
  688. }
  689. // Just use service account key to authenticate, since scopes may not be in place
  690. // for oauth. This also prevents us from making more requests.
  691. client := &http.Client{}
  692. // get the host and scheme to make the request
  693. parsedURL, err := url.Parse(r.URL)
  694. req, err := http.NewRequest(
  695. "GET",
  696. fmt.Sprintf("%s://%s/v2/%s/tags/list", parsedURL.Scheme, parsedURL.Host, repoName),
  697. nil,
  698. )
  699. if err != nil {
  700. return nil, err
  701. }
  702. req.SetBasicAuth(string(basic.Username), string(basic.Password))
  703. resp, err := client.Do(req)
  704. if err != nil {
  705. return nil, err
  706. }
  707. gcrResp := gcrImageResp{}
  708. if err := json.NewDecoder(resp.Body).Decode(&gcrResp); err != nil {
  709. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  710. }
  711. res := make([]*ptypes.Image, 0)
  712. for _, tag := range gcrResp.Tags {
  713. res = append(res, &ptypes.Image{
  714. RepositoryName: repoName,
  715. Tag: tag,
  716. })
  717. }
  718. return res, nil
  719. }
  720. type dockerHubImageResult struct {
  721. Name string `json:"name"`
  722. }
  723. type dockerHubImageResp struct {
  724. Results []dockerHubImageResult `json:"results"`
  725. }
  726. type dockerHubLoginReq struct {
  727. Username string `json:"username"`
  728. Password string `json:"password"`
  729. }
  730. type dockerHubLoginResp struct {
  731. Token string `json:"token"`
  732. }
  733. func (r *Registry) listDockerHubImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
  734. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  735. r.ProjectID,
  736. r.BasicIntegrationID,
  737. )
  738. if err != nil {
  739. return nil, err
  740. }
  741. client := &http.Client{}
  742. // first, make a request for the access token
  743. data, err := json.Marshal(&dockerHubLoginReq{
  744. Username: string(basic.Username),
  745. Password: string(basic.Password),
  746. })
  747. if err != nil {
  748. return nil, err
  749. }
  750. req, err := http.NewRequest(
  751. "POST",
  752. "https://hub.docker.com/v2/users/login",
  753. strings.NewReader(string(data)),
  754. )
  755. if err != nil {
  756. return nil, err
  757. }
  758. req.Header.Add("Content-Type", "application/json")
  759. resp, err := client.Do(req)
  760. if err != nil {
  761. return nil, err
  762. }
  763. tokenObj := dockerHubLoginResp{}
  764. if err := json.NewDecoder(resp.Body).Decode(&tokenObj); err != nil {
  765. return nil, fmt.Errorf("Could not decode Dockerhub token from response: %v", err)
  766. }
  767. req, err = http.NewRequest(
  768. "GET",
  769. fmt.Sprintf("https://hub.docker.com/v2/repositories/%s/tags", strings.Split(r.URL, "docker.io/")[1]),
  770. nil,
  771. )
  772. if err != nil {
  773. return nil, err
  774. }
  775. req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", tokenObj.Token))
  776. resp, err = client.Do(req)
  777. if err != nil {
  778. return nil, err
  779. }
  780. imageResp := dockerHubImageResp{}
  781. if err := json.NewDecoder(resp.Body).Decode(&imageResp); err != nil {
  782. return nil, fmt.Errorf("Could not read private registry repositories: %v", err)
  783. }
  784. res := make([]*ptypes.Image, 0)
  785. for _, result := range imageResp.Results {
  786. res = append(res, &ptypes.Image{
  787. RepositoryName: repoName,
  788. Tag: result.Name,
  789. })
  790. }
  791. return res, nil
  792. }
  793. // GetDockerConfigJSON returns a dockerconfigjson file contents with "auths"
  794. // populated.
  795. func (r *Registry) GetDockerConfigJSON(
  796. repo repository.Repository,
  797. doAuth *oauth2.Config, // only required if using DOCR
  798. ) ([]byte, error) {
  799. var conf *configfile.ConfigFile
  800. var err error
  801. // switch on the auth mechanism to get a token
  802. if r.AWSIntegrationID != 0 {
  803. conf, err = r.getECRDockerConfigFile(repo)
  804. }
  805. if r.GCPIntegrationID != 0 {
  806. conf, err = r.getGCRDockerConfigFile(repo)
  807. }
  808. if r.DOIntegrationID != 0 {
  809. conf, err = r.getDOCRDockerConfigFile(repo, doAuth)
  810. }
  811. if r.BasicIntegrationID != 0 {
  812. conf, err = r.getPrivateRegistryDockerConfigFile(repo)
  813. }
  814. if r.AzureIntegrationID != 0 {
  815. conf, err = r.getACRDockerConfigFile(repo)
  816. }
  817. if err != nil {
  818. return nil, err
  819. }
  820. return json.Marshal(conf)
  821. }
  822. func (r *Registry) getECRDockerConfigFile(
  823. repo repository.Repository,
  824. ) (*configfile.ConfigFile, error) {
  825. aws, err := repo.AWSIntegration().ReadAWSIntegration(
  826. r.ProjectID,
  827. r.AWSIntegrationID,
  828. )
  829. if err != nil {
  830. return nil, err
  831. }
  832. sess, err := aws.GetSession()
  833. if err != nil {
  834. return nil, err
  835. }
  836. ecrSvc := ecr.New(sess)
  837. output, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
  838. if err != nil {
  839. return nil, err
  840. }
  841. token := *output.AuthorizationData[0].AuthorizationToken
  842. decodedToken, err := base64.StdEncoding.DecodeString(token)
  843. if err != nil {
  844. return nil, err
  845. }
  846. parts := strings.SplitN(string(decodedToken), ":", 2)
  847. if len(parts) < 2 {
  848. return nil, err
  849. }
  850. key := r.URL
  851. if !strings.Contains(key, "http") {
  852. key = "https://" + key
  853. }
  854. return &configfile.ConfigFile{
  855. AuthConfigs: map[string]types.AuthConfig{
  856. key: {
  857. Username: parts[0],
  858. Password: parts[1],
  859. Auth: token,
  860. },
  861. },
  862. }, nil
  863. }
  864. func (r *Registry) getGCRDockerConfigFile(
  865. repo repository.Repository,
  866. ) (*configfile.ConfigFile, error) {
  867. gcp, err := repo.GCPIntegration().ReadGCPIntegration(
  868. r.ProjectID,
  869. r.GCPIntegrationID,
  870. )
  871. if err != nil {
  872. return nil, err
  873. }
  874. key := r.URL
  875. if !strings.Contains(key, "http") {
  876. key = "https://" + key
  877. }
  878. parsedURL, _ := url.Parse(key)
  879. return &configfile.ConfigFile{
  880. AuthConfigs: map[string]types.AuthConfig{
  881. parsedURL.Host: {
  882. Username: "_json_key",
  883. Password: string(gcp.GCPKeyData),
  884. Auth: generateAuthToken("_json_key", string(gcp.GCPKeyData)),
  885. },
  886. },
  887. }, nil
  888. }
  889. func (r *Registry) getDOCRDockerConfigFile(
  890. repo repository.Repository,
  891. doAuth *oauth2.Config,
  892. ) (*configfile.ConfigFile, error) {
  893. oauthInt, err := repo.OAuthIntegration().ReadOAuthIntegration(
  894. r.ProjectID,
  895. r.DOIntegrationID,
  896. )
  897. if err != nil {
  898. return nil, err
  899. }
  900. tok, _, err := oauth.GetAccessToken(oauthInt.SharedOAuthModel, doAuth, oauth.MakeUpdateOAuthIntegrationTokenFunction(oauthInt, repo))
  901. if err != nil {
  902. return nil, err
  903. }
  904. key := r.URL
  905. if !strings.Contains(key, "http") {
  906. key = "https://" + key
  907. }
  908. parsedURL, _ := url.Parse(key)
  909. return &configfile.ConfigFile{
  910. AuthConfigs: map[string]types.AuthConfig{
  911. parsedURL.Host: {
  912. Username: tok,
  913. Password: tok,
  914. Auth: generateAuthToken(tok, tok),
  915. },
  916. },
  917. }, nil
  918. }
  919. func (r *Registry) getPrivateRegistryDockerConfigFile(
  920. repo repository.Repository,
  921. ) (*configfile.ConfigFile, error) {
  922. basic, err := repo.BasicIntegration().ReadBasicIntegration(
  923. r.ProjectID,
  924. r.BasicIntegrationID,
  925. )
  926. if err != nil {
  927. return nil, err
  928. }
  929. key := r.URL
  930. if !strings.Contains(key, "http") {
  931. key = "https://" + key
  932. }
  933. parsedURL, _ := url.Parse(key)
  934. authConfigKey := parsedURL.Host
  935. if strings.Contains(r.URL, "index.docker.io") {
  936. authConfigKey = "https://index.docker.io/v1/"
  937. }
  938. return &configfile.ConfigFile{
  939. AuthConfigs: map[string]types.AuthConfig{
  940. authConfigKey: {
  941. Username: string(basic.Username),
  942. Password: string(basic.Password),
  943. Auth: generateAuthToken(string(basic.Username), string(basic.Password)),
  944. },
  945. },
  946. }, nil
  947. }
  948. func (r *Registry) getACRDockerConfigFile(
  949. repo repository.Repository,
  950. ) (*configfile.ConfigFile, error) {
  951. username, pw, err := r.GetACRCredentials(repo)
  952. if err != nil {
  953. return nil, err
  954. }
  955. key := r.URL
  956. if !strings.Contains(key, "http") {
  957. key = "https://" + key
  958. }
  959. parsedURL, _ := url.Parse(key)
  960. return &configfile.ConfigFile{
  961. AuthConfigs: map[string]types.AuthConfig{
  962. parsedURL.Host: {
  963. Username: string(username),
  964. Password: string(pw),
  965. Auth: generateAuthToken(string(username), string(pw)),
  966. },
  967. },
  968. }, nil
  969. }
  970. func generateAuthToken(username, password string) string {
  971. return base64.StdEncoding.EncodeToString([]byte(username + ":" + password))
  972. }