diff --git a/plugins/secrets/plugin.go b/plugins/secrets/plugin.go deleted file mode 100644 index 0ec22c5e9..000000000 --- a/plugins/secrets/plugin.go +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2018 Drone.IO Inc -// Use of this software is governed by the Drone Enterpise License -// that can be found in the LICENSE file. - -package secrets - -import ( - "github.com/drone/drone/model" - "github.com/drone/drone/store" -) - -// NewDefault returns the Store wrapped as a Service. -func NewDefault(store store.Store) model.SecretService { - return New(store) -} - -// Plugin defines the required interface for implementing a remote -// secret plugin and sourcing secrets from an external source. -type Plugin interface { - SecretListBuild(*model.Repo, *model.Build) ([]*model.Secret, error) -} - -// Extend exetends the base secret service with the plugin. -func Extend(base model.SecretService, with Plugin) model.SecretService { - return &extender{base, with} -} - -type extender struct { - model.SecretService - plugin Plugin -} - -// extends the base secret service and combines the secret list with the -// secret list returned by the plugin. -func (e *extender) SecretListBuild(repo *model.Repo, build *model.Build) ([]*model.Secret, error) { - base, err := e.SecretService.SecretListBuild(repo, build) - if err != nil { - return nil, err - } - with, err := e.plugin.SecretListBuild(repo, build) - if err != nil { - return nil, err - } - for _, secret := range base { - with = append(with, secret) - } - return with, nil -} diff --git a/plugins/secrets/plugin_test.go b/plugins/secrets/plugin_test.go deleted file mode 100644 index 3f411aea6..000000000 --- a/plugins/secrets/plugin_test.go +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright 2018 Drone.IO Inc -// Use of this software is governed by the Drone Enterpise License -// that can be found in the LICENSE file. - -package secrets - -import ( - "testing" - - "github.com/drone/drone/model" -) - -func TestExtends(t *testing.T) { - base := &mocker{} - base.list = []*model.Secret{ - {Name: "foo"}, - {Name: "bar"}, - } - - with := &mocker{} - with.list = []*model.Secret{ - {Name: "baz"}, - {Name: "qux"}, - } - - extended := Extend(base, with) - list, err := extended.SecretListBuild(nil, nil) - if err != nil { - t.Errorf("Expected combined secret list, got error %q", err) - } - - if got, want := list[0], with.list[0]; got != want { - t.Errorf("Expected correct precedence. Want %s, got %s", want.Name, got.Name) - } - if got, want := list[1], with.list[1]; got != want { - t.Errorf("Expected correct precedence. Want %s, got %s", want.Name, got.Name) - } - if got, want := list[2], base.list[0]; got != want { - t.Errorf("Expected correct precedence. Want %s, got %s", want.Name, got.Name) - } - if got, want := list[3], base.list[1]; got != want { - t.Errorf("Expected correct precedence. Want %s, got %s", want.Name, got.Name) - } -} - -type mocker struct { - list []*model.Secret - error error -} - -func (m *mocker) SecretFind(*model.Repo, string) (*model.Secret, error) { - return nil, nil -} -func (m *mocker) SecretList(*model.Repo) ([]*model.Secret, error) { - return nil, nil -} -func (m *mocker) SecretListBuild(*model.Repo, *model.Build) ([]*model.Secret, error) { - return m.list, m.error -} -func (m *mocker) SecretCreate(*model.Repo, *model.Secret) error { - return nil -} -func (m *mocker) SecretUpdate(*model.Repo, *model.Secret) error { - return nil -} -func (m *mocker) SecretDelete(*model.Repo, string) error { - return nil -} diff --git a/plugins/secrets/vault/fixtures/fakeJwt b/plugins/secrets/vault/fixtures/fakeJwt deleted file mode 100644 index 1e3abd126..000000000 --- a/plugins/secrets/vault/fixtures/fakeJwt +++ /dev/null @@ -1 +0,0 @@ -fakeJwt diff --git a/plugins/secrets/vault/kubernetes.go b/plugins/secrets/vault/kubernetes.go deleted file mode 100644 index 32a88f500..000000000 --- a/plugins/secrets/vault/kubernetes.go +++ /dev/null @@ -1,48 +0,0 @@ -package vault - -import ( - "fmt" - "github.com/drone/drone/plugins/internal" - "io/ioutil" - "time" -) - -/* -Vault JSON Response -{ - "auth": { - "client_token" = "token", - "lease_duration" = 1234 - } -} -*/ -type vaultAuth struct { - Token string `json:"client_token"` - Lease int `json:"lease_duration"` -} -type vaultResp struct { - Auth vaultAuth -} - -func getKubernetesToken(addr, role, mount, tokenFile string) (string, time.Duration, error) { - b, err := ioutil.ReadFile(tokenFile) - if err != nil { - return "", 0, err - } - - var resp vaultResp - path := fmt.Sprintf("%s/v1/auth/%s/login", addr, mount) - data := map[string]string{ - "jwt": string(b), - "role": role, - } - - err = internal.Send("POST", path, data, &resp) - if err != nil { - return "", 0, err - } - - ttl := time.Duration(resp.Auth.Lease) * time.Second - - return resp.Auth.Token, ttl, nil -} diff --git a/plugins/secrets/vault/kubernetes_test.go b/plugins/secrets/vault/kubernetes_test.go deleted file mode 100644 index 42cfe6370..000000000 --- a/plugins/secrets/vault/kubernetes_test.go +++ /dev/null @@ -1,69 +0,0 @@ -package vault - -import ( - "encoding/json" - "fmt" - "io/ioutil" - "net/http" - "net/http/httptest" - "testing" - "time" -) - -func TestGetKubernetesToken(t *testing.T) { - fakeRole := "fakeRole" - fakeMountPoint := "kubernetes" - fakeJwtFile := "fixtures/fakeJwt" - b, _ := ioutil.ReadFile(fakeJwtFile) - fakeJwt := string(b) - fakeClientToken := "fakeClientToken" - fakeLeaseSeconds := 86400 - fakeLeaseDuration := time.Duration(fakeLeaseSeconds) * time.Second - fakeResp := fmt.Sprintf("{\"auth\": {\"client_token\": \"%s\", \"lease_duration\": %d}}", fakeClientToken, fakeLeaseSeconds) - expectedPath := "/v1/auth/kubernetes/login" - - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - if r.Method != "POST" { - t.Errorf("Expected 'POST' request, got '%s'", r.Method) - } - if r.URL.EscapedPath() != expectedPath { - t.Errorf("Expected request to '%s', got '%s'", expectedPath, r.URL.EscapedPath()) - } - - var postdata struct { - Jwt string - Role string - } - err := json.NewDecoder(r.Body).Decode(&postdata) - if err != nil { - t.Errorf("Encountered error parsing request JSON: %s", err) - } - - jwt := postdata.Jwt - - if jwt != fakeJwt { - t.Errorf("Expected request to have jwt with value '%s', got: '%s'", fakeJwt, jwt) - } - role := postdata.Role - if role != fakeRole { - t.Errorf("Expected request to have role with value '%s', got: '%s'", fakeRole, role) - } - - fmt.Fprintf(w, fakeResp) - })) - defer ts.Close() - - url := ts.URL - token, ttl, err := getKubernetesToken(url, fakeRole, fakeMountPoint, fakeJwtFile) - if err != nil { - t.Errorf("getKubernetesToken returned an error: %s", err) - } - - if token != fakeClientToken { - t.Errorf("Expected returned token to have value '%s', got: '%s'", fakeClientToken, token) - } - if ttl != fakeLeaseDuration { - t.Errorf("Expected TTL to have value '%s', got: '%s'", fakeLeaseDuration.Seconds(), ttl.Seconds()) - } -} diff --git a/plugins/secrets/vault/opts.go b/plugins/secrets/vault/opts.go deleted file mode 100644 index 7b4eaa7eb..000000000 --- a/plugins/secrets/vault/opts.go +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright 2018 Drone.IO Inc -// Use of this software is governed by the Drone Enterpise License -// that can be found in the LICENSE file. - -package vault - -import "time" - -// Opts sets custom options for the vault client. -type Opts func(v *vault) - -// WithTTL returns an options that sets a TTL used to -// refresh periodic tokens. -func WithTTL(d time.Duration) Opts { - return func(v *vault) { - v.ttl = d - } -} - -// WithRenewal returns an options that sets the renewal -// period used to refresh periodic tokens -func WithRenewal(d time.Duration) Opts { - return func(v *vault) { - v.renew = d - } -} - -// WithAuth returns an options that sets the vault -// method to use for authentication -func WithAuth(method string) Opts { - return func(v *vault) { - v.auth = method - } -} - -// WithKubernetes returns an options that sets -// kubernetes-auth parameters required to retrieve -// an initial vault token -func WithKubernetesAuth(addr, role, mount string) Opts { - return func(v *vault) { - v.kubeAuth.addr = addr - v.kubeAuth.role = role - v.kubeAuth.mount = mount - } -} diff --git a/plugins/secrets/vault/opts_test.go b/plugins/secrets/vault/opts_test.go deleted file mode 100644 index 873907151..000000000 --- a/plugins/secrets/vault/opts_test.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright 2018 Drone.IO Inc -// Use of this software is governed by the Drone Enterpise License -// that can be found in the LICENSE file. - -package vault - -import ( - "testing" - "time" -) - -func TestWithTTL(t *testing.T) { - v := new(vault) - opt := WithTTL(time.Hour) - opt(v) - if got, want := v.ttl, time.Hour; got != want { - t.Errorf("Want ttl %v, got %v", want, got) - } -} - -func TestWithRenewal(t *testing.T) { - v := new(vault) - opt := WithRenewal(time.Hour) - opt(v) - if got, want := v.renew, time.Hour; got != want { - t.Errorf("Want renewal %v, got %v", want, got) - } -} - -func TestWithAuth(t *testing.T) { - v := new(vault) - method := "kubernetes" - opt := WithAuth(method) - opt(v) - if got, want := v.auth, method; got != want { - t.Errorf("Want auth %v, got %v", want, got) - } -} - -func TestWithKubernetesAuth(t *testing.T) { - v := new(vault) - addr := "https://address.fake" - role := "fakeRole" - mount := "kubernetes" - opt := WithKubernetesAuth(addr, role, mount) - opt(v) - if got, want := v.kubeAuth.addr, addr; got != want { - t.Errorf("Want addr %v, got %v", want, got) - } - if got, want := v.kubeAuth.role, role; got != want { - t.Errorf("Want role %v, got %v", want, got) - } - if got, want := v.kubeAuth.mount, mount; got != want { - t.Errorf("Want mount %v, got %v", want, got) - } -} diff --git a/plugins/secrets/vault/vault.go b/plugins/secrets/vault/vault.go deleted file mode 100644 index 5afab8b14..000000000 --- a/plugins/secrets/vault/vault.go +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright 2018 Drone.IO Inc -// Use of this software is governed by the Drone Enterpise License -// that can be found in the LICENSE file. - -package vault - -import ( - "errors" - "path" - "strings" - "time" - - "github.com/Sirupsen/logrus" - "github.com/drone/drone/model" - "github.com/drone/drone/plugins/secrets" - - "github.com/hashicorp/vault/api" - "gopkg.in/yaml.v2" -) - -// yaml configuration representation -// -// secrets: -// docker_username: -// file: path/to/docker/username -// docker_password: -// file: path/to/docker/password -// -type vaultConfig struct { - Secrets map[string]struct { - Driver string - DriverOpts struct { - Path string - Key string - } `yaml:"driver_opts"` - - // deprecated. do not use. - Vault string - Path string - File string - } -} - -type vault struct { - store model.ConfigStore - client *api.Client - ttl time.Duration - renew time.Duration - auth string - kubeAuth kubeAuth - done chan struct{} -} - -type kubeAuth struct { - addr, role, mount string -} - -// New returns a new store with secrets loaded from vault. -func New(store model.ConfigStore, opts ...Opts) (secrets.Plugin, error) { - client, err := api.NewClient(nil) - if err != nil { - return nil, err - } - v := &vault{ - store: store, - client: client, - } - for _, opt := range opts { - opt(v) - } - if v.auth == "kubernetes" { - err = v.initKubernetes() - if err != nil { - return nil, err - } - } - v.start() // start the refresh process. - return v, nil -} - -func (v *vault) initKubernetes() error { - if v.renew == 0 { - return errors.New("vault: token renewal not configured") - } - token, ttl, err := getKubernetesToken( - v.kubeAuth.addr, - v.kubeAuth.role, - v.kubeAuth.mount, - "/var/run/secrets/kubernetes.io/serviceaccount/token", - ) - if err != nil { - logrus.Debugf("vault: failed to obtain token via kubernetes-auth backend: %s", err) - return err - } - - v.client.SetToken(token) - v.ttl = ttl - return nil -} - -func (v *vault) SecretListBuild(repo *model.Repo, build *model.Build) ([]*model.Secret, error) { - return v.list(repo, build) -} - -func (v *vault) list(repo *model.Repo, build *model.Build) ([]*model.Secret, error) { - conf, err := v.store.ConfigLoad(build.ConfigID) - if err != nil { - return nil, err - } - var ( - in = []byte(conf.Data) - out = new(vaultConfig) - - secrets []*model.Secret - ) - err = yaml.Unmarshal(in, out) - if err != nil { - return nil, err - } - for key, val := range out.Secrets { - var path, field string - switch { - case val.Path != "": - path = val.Path - case val.File != "": - path = val.File - case val.Vault != "": - path = val.Vault - case val.DriverOpts.Path != "": - path = val.DriverOpts.Path - field = val.DriverOpts.Key - } - if field == "" { - field = "value" - } - - if path == "" { - continue - } - - logrus.Debugf("vault: read secret: %s", path) - - vaultSecret, err := v.get(path, field) - if err != nil { - logrus.Debugf("vault: read secret failed: %s: %s", path, err) - return nil, err - } - if vaultSecret == nil { - logrus.Debugf("vault: read secret failed: %s: not found or empty value", path) - continue - } - if !vaultSecret.Match(repo.FullName) { - logrus.Debugf("vault: read secret: %s: restricted: %s", path, repo.FullName) - continue - } - - logrus.Debugf("vault: read secret success: %s", err) - - secrets = append(secrets, &model.Secret{ - Name: key, - Value: vaultSecret.Value, - Events: vaultSecret.Event, - Images: vaultSecret.Image, - }) - } - return secrets, nil -} - -func (v *vault) get(path, key string) (*vaultSecret, error) { - secret, err := v.client.Logical().Read(path) - if err != nil { - return nil, err - } - if secret == nil || secret.Data == nil { - return nil, nil - } - return parseVaultSecret(secret.Data, key), nil -} - -// start starts the renewal loop. -func (v *vault) start() { - if v.renew == 0 || v.ttl == 0 { - logrus.Debugf("vault: token renewal disabled") - return - } - if v.done != nil { - close(v.done) - } - logrus.Infof("vault: token renewal enabled: renew every %v", v.renew) - v.done = make(chan struct{}) - if v.renew != 0 { - go v.renewLoop() - } -} - -// stop stops the renewal loop. -func (v *vault) stop() { - close(v.done) -} - -func (v *vault) renewLoop() { - for { - select { - case <-time.After(v.renew): - incr := int(v.ttl / time.Second) - - logrus.Debugf("vault: refreshing token: increment %v", v.ttl) - _, err := v.client.Auth().Token().RenewSelf(incr) - if err != nil { - logrus.Errorf("vault: refreshing token failed: %s", err) - } else { - logrus.Debugf("vault: refreshing token succeeded") - } - case <-v.done: - return - } - } -} - -type vaultSecret struct { - Value string - Image []string - Event []string - Repo []string -} - -func parseVaultSecret(data map[string]interface{}, key string) *vaultSecret { - secret := new(vaultSecret) - - if vvalue, ok := data[key]; ok { - if svalue, ok := vvalue.(string); ok { - secret.Value = svalue - } - } - if vimage, ok := data["image"]; ok { - if simage, ok := vimage.(string); ok { - secret.Image = strings.Split(simage, ",") - } - } - if vevent, ok := data["event"]; ok { - if sevent, ok := vevent.(string); ok { - secret.Event = strings.Split(sevent, ",") - } - } - if vrepo, ok := data["repo"]; ok { - if srepo, ok := vrepo.(string); ok { - secret.Repo = strings.Split(srepo, ",") - } - } - if secret.Event == nil { - secret.Event = []string{} - } - if secret.Image == nil { - secret.Image = []string{} - } - if secret.Repo == nil { - secret.Repo = []string{} - } - return secret -} - -func (v *vaultSecret) Match(name string) bool { - if len(v.Repo) == 0 { - return true - } - for _, pattern := range v.Repo { - if ok, _ := path.Match(pattern, name); ok { - return true - } - } - return false -} diff --git a/plugins/secrets/vault/vault_test.go b/plugins/secrets/vault/vault_test.go deleted file mode 100644 index cceb5a98f..000000000 --- a/plugins/secrets/vault/vault_test.go +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2018 Drone.IO Inc -// Use of this software is governed by the Drone Enterpise License -// that can be found in the LICENSE file. - -package vault - -import ( - "os" - "reflect" - "testing" - - "github.com/hashicorp/vault/api" - "github.com/kr/pretty" -) - -// Use the following snippet to spin up a local vault -// server for integration testing: -// -// docker run --cap-add=IPC_LOCK -e 'VAULT_DEV_ROOT_TOKEN_ID=dummy' -p 8200:8200 vault -// export VAULT_ADDR=http://127.0.0.1:8200 -// export VAULT_TOKEN=dummy - -func TestVaultGet(t *testing.T) { - if os.Getenv("VAULT_TOKEN") == "" { - t.SkipNow() - return - } - - client, err := api.NewClient(nil) - if err != nil { - t.Error(err) - return - } - - _, err = client.Logical().Write("secret/testing/drone/a", map[string]interface{}{ - "value": "hello", - "fr": "bonjour", - "image": "golang", - "event": "push,pull_request", - "repo": "octocat/hello-world,github/*", - }) - if err != nil { - t.Error(err) - return - } - - plugin := vault{client: client} - secret, err := plugin.get("secret/testing/drone/a", "value") - if err != nil { - t.Error(err) - return - } - if got, want := secret.Value, "hello"; got != want { - t.Errorf("Expect secret value %s, got %s", want, got) - } - - secret, err = plugin.get("secret/testing/drone/a", "fr") - if err != nil { - t.Error(err) - return - } - if got, want := secret.Value, "bonjour"; got != want { - t.Errorf("Expect secret value %s, got %s", want, got) - } - - secret, err = plugin.get("secret/testing/drone/404", "value") - if err != nil { - t.Errorf("Expect silent failure when secret does not exist, got %s", err) - } - if secret != nil { - t.Errorf("Expect nil secret when path does not exist") - } -} - -func TestVaultSecretParse(t *testing.T) { - data := map[string]interface{}{ - "value": "password", - "event": "push,tag", - "image": "plugins/s3,plugins/ec2", - "repo": "octocat/hello-world,github/*", - } - want := vaultSecret{ - Value: "password", - Event: []string{"push", "tag"}, - Image: []string{"plugins/s3", "plugins/ec2"}, - Repo: []string{"octocat/hello-world", "github/*"}, - } - got := parseVaultSecret(data, "value") - if !reflect.DeepEqual(want, *got) { - t.Errorf("Failed read Secret.Data") - pretty.Fdiff(os.Stderr, want, got) - } -} - -func TestVaultSecretMatch(t *testing.T) { - secret := vaultSecret{ - Repo: []string{"octocat/hello-world", "github/*"}, - } - if secret.Match("octocat/*") { - t.Errorf("Expect octocat/* does not match") - } - if !secret.Match("octocat/hello-world") { - t.Errorf("Expect octocat/hello-world does match") - } - if !secret.Match("github/hello-world") { - t.Errorf("Expect github/hello-world does match wildcard") - } -}