// 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 secret import ( "context" "time" "github.com/drone/drone-yaml/yaml" "github.com/drone/drone/core" "github.com/drone/drone/logger" "github.com/drone/drone-go/drone" "github.com/drone/drone-go/plugin/secret" ) // External returns a new external Secret controller. func External(endpoint, secret string, skipVerify bool) core.SecretService { return &externalController{ endpoint: endpoint, secret: secret, skipVerify: skipVerify, } } type externalController struct { endpoint string secret string skipVerify bool } func (c *externalController) Find(ctx context.Context, in *core.SecretArgs) (*core.Secret, error) { if c.endpoint == "" { return nil, nil } logger := logger.FromContext(ctx). WithField("name", in.Name). WithField("kind", "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, in.Name) if !ok { logger.Trace("secret: external: no matching secret") return nil, nil } // 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("secret: external: 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 == "" { logger.Trace("secret: external: secret disabled for pull requests") 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.Trace("secret: external: restricted from forks") return nil, nil } logger.Trace("secret: external: found matching secret") return &core.Secret{ Name: in.Name, Data: res.Data, PullRequest: res.Pull, }, 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) drone.Repo { return drone.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) drone.Build { return drone.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, } }