Skip to content
Permalink
Browse files

support for per-organization secrets

  • Loading branch information...
bradrydzewski committed Apr 15, 2019
1 parent 727177d commit 96132e3d0a625a63dfff3ac77ecd21c0245f1775
@@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- support for Cron job name in Yaml when block, by [@bradrydzewski](https://github.com/bradrydzewski). [#2628](https://github.com/drone/drone/issues/2628).
- sqlite username column changed to case-insensitive, by [@bradrydzewski](https://github.com/bradrydzewski).
- endpoint to purge repository from database, by [@bradrydzewski](https://github.com/bradrydzewski).
- support for per-organization secrets, by [@bradrydzewski](https://github.com/bradrydzewski).
- update drone-yaml from version 1.0.6 to 1.0.8.
- update drone-runtime from version 1.0.4 to 1.0.6.

@@ -25,6 +25,7 @@ import (
"github.com/drone/drone/store/perm"
"github.com/drone/drone/store/repos"
"github.com/drone/drone/store/secret"
"github.com/drone/drone/store/secret/global"
"github.com/drone/drone/store/shared/db"
"github.com/drone/drone/store/shared/encrypt"
"github.com/drone/drone/store/stage"
@@ -47,6 +48,7 @@ var storeSet = wire.NewSet(
cron.New,
perm.New,
secret.New,
global.New,
step.New,
)

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -33,7 +33,9 @@ type (
Secret struct {
ID int64 `json:"id,omitempty"`
RepoID int64 `json:"repo_id,omitempty"`
Namespace string `json:"repo_namespace,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Data string `json:"data,omitempty"`
PullRequest bool `json:"pull_request,omitempty"`
PullRequestPush bool `json:"pull_request_push,omitempty"`
@@ -69,6 +71,32 @@ type (
Delete(context.Context, *Secret) error
}

// GlobalSecretStore manages global secrets accessible to
// all repositories in the system.
GlobalSecretStore interface {
// List returns a secret list from the datastore.
List(ctx context.Context, namespace string) ([]*Secret, error)

// ListAll returns a secret list from the datastore
// for all namespaces.
ListAll(ctx context.Context) ([]*Secret, error)

// Find returns a secret from the datastore.
Find(ctx context.Context, id int64) (*Secret, error)

// FindName returns a secret from the datastore.
FindName(ctx context.Context, namespace, name string) (*Secret, error)

// Create persists a new secret to the datastore.
Create(ctx context.Context, secret *Secret) error

// Update persists an updated secret to the datastore.
Update(ctx context.Context, secret *Secret) error

// Delete deletes a secret from the datastore.
Delete(ctx context.Context, secret *Secret) error
}

// SecretService provides secrets from an external service.
SecretService interface {
// Find returns a named secret from the global remote service.
@@ -95,7 +123,9 @@ func (s *Secret) Copy() *Secret {
return &Secret{
ID: s.ID,
RepoID: s.RepoID,
Namespace: s.Namespace,
Name: s.Name,
Type: s.Type,
PullRequest: s.PullRequest,
PullRequestPush: s.PullRequestPush,
}
@@ -47,6 +47,8 @@ func TestSecretSafeCopy(t *testing.T) {
ID: 1,
RepoID: 2,
Name: "docker_password",
Namespace: "octocat",
Type: "",
Data: "correct-horse-battery-staple",
PullRequest: true,
PullRequestPush: true,
@@ -61,6 +63,9 @@ func TestSecretSafeCopy(t *testing.T) {
if got, want := after.Name, before.Name; got != want {
t.Errorf("Want secret Name %s, got %s", want, got)
}
if got, want := after.Namespace, before.Namespace; got != want {
t.Errorf("Want secret Namespace %s, got %s", want, got)
}
if got, want := after.PullRequest, before.PullRequest; got != want {
t.Errorf("Want secret PullRequest %v, got %v", want, got)
}
1 go.sum
@@ -65,6 +65,7 @@ github.com/drone/go-login v1.0.3 h1:YmZMUoWWd3QrgmobC1DcExFjW7w2ZEBO1R1VeeobIRU=
github.com/drone/go-login v1.0.3/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
github.com/drone/go-login v1.0.4-0.20190308175602-213d1719faed h1:Y0qiKFf6gsgTRTQS1roMh7kKVyrx+HSQmFsIgcZsHsM=
github.com/drone/go-login v1.0.4-0.20190308175602-213d1719faed/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2 h1:RGpgNkowJc5LAVn/ZONx70qmnaTA0z/3hHPzTBdAEO8=
github.com/drone/go-login v1.0.4-0.20190311170324-2a4df4f242a2/go.mod h1:FLxy9vRzLbyBxoCJYxGbG9R0WGn6OyuvBmAtYNt43uw=
github.com/drone/go-scm v1.2.0 h1:ezb8xCvMHX99cSOf3WPI2bmYS6tDVTTap9BiPsPmmXg=
github.com/drone/go-scm v1.2.0/go.mod h1:YT4FxQ3U/ltdCrBJR9B0tRpJ1bYA/PM3NyaLE/rYIvw=
@@ -34,6 +34,7 @@ import (
"github.com/drone/drone/handler/api/repos/encrypt"
"github.com/drone/drone/handler/api/repos/secrets"
"github.com/drone/drone/handler/api/repos/sign"
globalsecrets "github.com/drone/drone/handler/api/secrets"
"github.com/drone/drone/handler/api/system"
"github.com/drone/drone/handler/api/user"
"github.com/drone/drone/handler/api/users"
@@ -58,6 +59,7 @@ func New(
commits core.CommitService,
cron core.CronStore,
events core.Pubsub,
globals core.GlobalSecretStore,
hooks core.HookService,
logs core.LogStore,
license *core.License,
@@ -83,6 +85,7 @@ func New(
Cron: cron,
Commits: commits,
Events: events,
Globals: globals,
Hooks: hooks,
Logs: logs,
License: license,
@@ -111,6 +114,7 @@ type Server struct {
Cron core.CronStore
Commits core.CommitService
Events core.Pubsub
Globals core.GlobalSecretStore
Hooks core.HookService
Logs core.LogStore
License *core.License
@@ -298,6 +302,16 @@ func (s Server) Handler() http.Handler {
r.Get("/incomplete", globalbuilds.HandleIncomplete(s.Repos))
})

r.Route("/secrets", func(r chi.Router) {
r.Use(acl.AuthorizeAdmin)
r.Get("/", globalsecrets.HandleAll(s.Globals))
r.Get("/{namespace}", globalsecrets.HandleList(s.Globals))
r.Post("/{namespace}", globalsecrets.HandleCreate(s.Globals))
r.Get("/{namespace}/{name}", globalsecrets.HandleFind(s.Globals))
r.Patch("/{namespace}/{name}", globalsecrets.HandleUpdate(s.Globals))
r.Delete("/{namespace}/{name}", globalsecrets.HandleDelete(s.Globals))
})

r.Route("/system", func(r chi.Router) {
r.Use(acl.AuthorizeAdmin)
// r.Get("/license", system.HandleLicense())
@@ -0,0 +1,33 @@
// 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 secrets

import (
"net/http"

"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
)

// HandleAll returns an http.HandlerFunc that writes a json-encoded
// list of secrets to the response body.
func HandleAll(secrets core.GlobalSecretStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
list, err := secrets.ListAll(r.Context())
if err != nil {
render.NotFound(w, err)
return
}
// the secret list is copied and the secret value is
// removed from the response.
secrets := []*core.Secret{}
for _, secret := range list {
secrets = append(secrets, secret.Copy())
}
render.JSON(w, secrets, 200)
}
}
@@ -0,0 +1,65 @@
// 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 secrets

import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"

"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/errors"
"github.com/drone/drone/mock"

"github.com/golang/mock/gomock"
"github.com/google/go-cmp/cmp"
)

func TestHandleAll(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()

secrets := mock.NewMockGlobalSecretStore(controller)
secrets.EXPECT().ListAll(gomock.Any()).Return(dummySecretList, nil)

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)

HandleAll(secrets).ServeHTTP(w, r)
if got, want := w.Code, http.StatusOK; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}

got, want := []*core.Secret{}, dummySecretListScrubbed
json.NewDecoder(w.Body).Decode(&got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}

func TestHandleAll_SecretListErr(t *testing.T) {
controller := gomock.NewController(t)
defer controller.Finish()

secrets := mock.NewMockGlobalSecretStore(controller)
secrets.EXPECT().ListAll(gomock.Any()).Return(nil, errors.ErrNotFound)

w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/", nil)

HandleAll(secrets).ServeHTTP(w, r)
if got, want := w.Code, http.StatusNotFound; want != got {
t.Errorf("Want response code %d, got %d", want, got)
}

got, want := new(errors.Error), errors.ErrNotFound
json.NewDecoder(w.Body).Decode(got)
if diff := cmp.Diff(got, want); len(diff) != 0 {
t.Errorf(diff)
}
}
@@ -0,0 +1,60 @@
// 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 secrets

import (
"encoding/json"
"net/http"

"github.com/drone/drone/core"
"github.com/drone/drone/handler/api/render"
"github.com/go-chi/chi"
)

type secretInput struct {
Type string `json:"type"`
Name string `json:"name"`
Data string `json:"data"`
PullRequest bool `json:"pull_request"`
PullRequestPush bool `json:"pull_request_push"`
}

// HandleCreate returns an http.HandlerFunc that processes http
// requests to create a new secret.
func HandleCreate(secrets core.GlobalSecretStore) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
in := new(secretInput)
err := json.NewDecoder(r.Body).Decode(in)
if err != nil {
render.BadRequest(w, err)
return
}

s := &core.Secret{
Namespace: chi.URLParam(r, "namespace"),
Name: in.Name,
Data: in.Data,
PullRequest: in.PullRequest,
PullRequestPush: in.PullRequestPush,
}

err = s.Validate()
if err != nil {
render.BadRequest(w, err)
return
}

err = secrets.Create(r.Context(), s)
if err != nil {
render.InternalError(w, err)
return
}

s = s.Copy()
render.JSON(w, s, 200)
}
}
Oops, something went wrong.

0 comments on commit 96132e3

Please sign in to comment.
You can’t perform that action at this time.