// Copyright 2019 Drone.IO Inc. All rights reserved. // Use of this source code is governed by the Drone Non-Commercial License // that can be found in the LICENSE file. // +build !oss package registry import ( "context" "time" "github.com/drone/drone-go/plugin/secret" "github.com/drone/drone-yaml/yaml" "github.com/drone/drone/core" "github.com/drone/drone/logger" "github.com/drone/drone/plugin/registry/auths" droneapi "github.com/drone/drone-go/drone" ) // External returns a new external Secret controller. func External(endpoint, secret string, skipVerify bool) core.RegistryService { return &externalController{ endpoint: endpoint, secret: secret, skipVerify: skipVerify, } } type externalController struct { endpoint string secret string skipVerify bool } func (c *externalController) List(ctx context.Context, in *core.RegistryArgs) ([]*core.Registry, error) { var results []*core.Registry for _, match := range in.Pipeline.PullSecrets { logger := logger.FromContext(ctx). WithField("name", match). WithField("kind", "secret"). WithField("secret", c.endpoint) logger.Trace("image_pull_secrets: find secret") // lookup the named secret in the manifest. If the // secret does not exist, return a nil variable, // allowing the next secret controller in the chain // to be invoked. path, name, ok := getExternal(in.Conf, match) if !ok { logger.Trace("image_pull_secrets: no matching secret resource in yaml") return nil, nil } logger = logger. WithField("get.path", path). WithField("get.name", name) // include a timeout to prevent an API call from // hanging the build process indefinitely. The // external service must return a request within // one minute. ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() req := &secret.Request{ Name: name, Path: path, Repo: toRepo(in.Repo), Build: toBuild(in.Build), } client := secret.Client(c.endpoint, c.secret, c.skipVerify) res, err := client.Find(ctx, req) if err != nil { logger.WithError(err).Trace("image_pull_secrets: cannot get secret") return nil, err } // if no error is returned and the secret is empty, // this indicates the client returned No Content, // and we should exit with no secret, but no error. if res.Data == "" { return nil, nil } // The secret can be restricted to non-pull request // events. If the secret is restricted, return // empty results. if (res.Pull == false && res.PullRequest == false) && in.Build.Event == core.EventPullRequest { logger.WithError(err).Trace("image_pull_secrets: pull_request access denied") return nil, nil } parsed, err := auths.ParseString(res.Data) if err != nil { return nil, err } logger.Trace("image_pull_secrets: found secret") results = append(results, parsed...) } return results, nil } func getExternal(manifest *yaml.Manifest, match string) (path, name string, ok bool) { for _, resource := range manifest.Resources { secret, ok := resource.(*yaml.Secret) if !ok { continue } if secret.Name != match { continue } if secret.Get.Name == "" && secret.Get.Path == "" { continue } return secret.Get.Path, secret.Get.Name, true } return } func toRepo(from *core.Repository) droneapi.Repo { return droneapi.Repo{ ID: from.ID, UID: from.UID, UserID: from.UserID, Namespace: from.Namespace, Name: from.Name, Slug: from.Slug, SCM: from.SCM, HTTPURL: from.HTTPURL, SSHURL: from.SSHURL, Link: from.Link, Branch: from.Branch, Private: from.Private, Visibility: from.Visibility, Active: from.Active, Config: from.Config, Trusted: from.Trusted, Protected: from.Protected, Timeout: from.Timeout, } } func toBuild(from *core.Build) droneapi.Build { return droneapi.Build{ ID: from.ID, RepoID: from.RepoID, Trigger: from.Trigger, Number: from.Number, Parent: from.Parent, Status: from.Status, Error: from.Error, Event: from.Event, Action: from.Action, Link: from.Link, Timestamp: from.Timestamp, Title: from.Title, Message: from.Message, Before: from.Before, After: from.After, Ref: from.Ref, Fork: from.Fork, Source: from.Source, Target: from.Target, Author: from.Author, AuthorName: from.AuthorName, AuthorEmail: from.AuthorEmail, AuthorAvatar: from.AuthorAvatar, Sender: from.Sender, Params: from.Params, Deploy: from.Deploy, Started: from.Started, Finished: from.Finished, Created: from.Created, Updated: from.Updated, Version: from.Version, } }