From 700977817624866f37b50a8f9f3376c20a66f35c Mon Sep 17 00:00:00 2001 From: Brad Rydzewski Date: Wed, 22 Oct 2014 00:13:47 -0700 Subject: [PATCH] added token to post-commit hooks --- Makefile | 2 +- plugin/remote/gitlab/gitlab.go | 2 +- server/datastore/database/database.go | 1 + server/datastore/migrate/migrate.go | 146 ++++++++++++++++++++++++++ server/handler/hook.go | 10 ++ server/handler/repo.go | 3 +- shared/model/commit.go | 2 +- shared/model/repo.go | 1 + shared/model/user.go | 4 +- shared/model/util.go | 6 +- shared/model/util_test.go | 8 +- 11 files changed, 172 insertions(+), 13 deletions(-) create mode 100644 server/datastore/migrate/migrate.go diff --git a/Makefile b/Makefile index 390fea96..9f0e4ff2 100644 --- a/Makefile +++ b/Makefile @@ -88,5 +88,5 @@ rpm: # DRONE_STAGING_USER -- the username used to login # DRONE_STAGING_KEY -- the identity file path (~/.ssh/id_rsa) deploy: - scp -i $$DRONE_STAGING_KEY debian/drone.deb $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST:/tmp + scp -i $$DRONE_STAGING_KEY packaging/output/drone.deb $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST:/tmp ssh -i $$DRONE_STAGING_KEY $$DRONE_STAGING_USER@$$DRONE_STAGING_HOST -- dpkg -i /tmp/drone.deb diff --git a/plugin/remote/gitlab/gitlab.go b/plugin/remote/gitlab/gitlab.go index 61423297..86028e64 100644 --- a/plugin/remote/gitlab/gitlab.go +++ b/plugin/remote/gitlab/gitlab.go @@ -135,7 +135,7 @@ func (r *Gitlab) Activate(user *model.User, repo *model.Repo, link string) error // append the repo owner / name to the hook url since gitlab // doesn't send this detail in the post-commit hook - link += "?owner=" + repo.Owner + "&name=" + repo.Name + link += "&owner=" + repo.Owner + "&name=" + repo.Name // add the hook return client.AddProjectHook(path, link, true, false, true) diff --git a/server/datastore/database/database.go b/server/datastore/database/database.go index dde1de65..6ca59d66 100644 --- a/server/datastore/database/database.go +++ b/server/datastore/database/database.go @@ -37,6 +37,7 @@ func Connect(driver, datasource string) (*sql.DB, error) { migration.DefaultSetVersion = migrate.SetVersion var migrations = []migration.Migrator{ migrate.Setup, + migrate.Migrate_20142110, } return migration.Open(driver, datasource, migrations) } diff --git a/server/datastore/migrate/migrate.go b/server/datastore/migrate/migrate.go new file mode 100644 index 00000000..42a3c57c --- /dev/null +++ b/server/datastore/migrate/migrate.go @@ -0,0 +1,146 @@ +package migrate + +import ( + "github.com/BurntSushi/migration" +) + +// Setup is the database migration function that +// will setup the initial SQL database structure. +func Setup(tx migration.LimitedTx) error { + var stmts = []string{ + blobTable, + userTable, + repoTable, + permTable, + commitTable, + } + for _, stmt := range stmts { + _, err := tx.Exec(transform(stmt)) + if err != nil { + return err + } + } + return nil +} + +// Migrate_20142110 is a database migration on Oct-10 2014. +func Migrate_20142110(tx migration.LimitedTx) error { + var stmts = []string{ + commitRepoIndex, // index the commit table repo_id column + repoTokenColumn, // add the repo token column + repoTokenUpdate, // update the repo token column to empty string + } + for _, stmt := range stmts { + _, err := tx.Exec(transform(stmt)) + if err != nil { + return err + } + } + return nil +} + +var userTable = ` +CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER PRIMARY KEY AUTOINCREMENT + ,user_remote VARCHAR(255) + ,user_login VARCHAR(255) + ,user_access VARCHAR(255) + ,user_secret VARCHAR(255) + ,user_name VARCHAR(255) + ,user_email VARCHAR(255) + ,user_gravatar VARCHAR(255) + ,user_token VARCHAR(255) + ,user_admin BOOLEAN + ,user_active BOOLEAN + ,user_syncing BOOLEAN + ,user_created INTEGER + ,user_updated INTEGER + ,user_synced INTEGER + ,UNIQUE(user_token) + ,UNIQUE(user_remote, user_login) +); +` + +var permTable = ` +CREATE TABLE IF NOT EXISTS perms ( + perm_id INTEGER PRIMARY KEY AUTOINCREMENT + ,user_id INTEGER + ,repo_id INTEGER + ,perm_read BOOLEAN + ,perm_write BOOLEAN + ,perm_admin BOOLEAN + ,perm_created INTEGER + ,perm_updated INTEGER + ,UNIQUE (repo_id, user_id) +); +` + +var repoTable = ` +CREATE TABLE IF NOT EXISTS repos ( + repo_id INTEGER PRIMARY KEY AUTOINCREMENT + ,user_id INTEGER + ,repo_remote VARCHAR(255) + ,repo_host VARCHAR(255) + ,repo_owner VARCHAR(255) + ,repo_name VARCHAR(255) + ,repo_url VARCHAR(1024) + ,repo_clone_url VARCHAR(255) + ,repo_git_url VARCHAR(255) + ,repo_ssh_url VARCHAR(255) + ,repo_active BOOLEAN + ,repo_private BOOLEAN + ,repo_privileged BOOLEAN + ,repo_post_commit BOOLEAN + ,repo_pull_request BOOLEAN + ,repo_public_key BLOB + ,repo_private_key BLOB + ,repo_params BLOB + ,repo_timeout INTEGER + ,repo_created INTEGER + ,repo_updated INTEGER + ,UNIQUE(repo_host, repo_owner, repo_name) +); +` + +var repoTokenColumn = ` +ALTER TABLE repos ADD COLUMN repo_token VARCHAR(40) +` + +var repoTokenUpdate = ` +UPDATE repos SET repo_token = ''; +` + +var commitTable = ` +CREATE TABLE IF NOT EXISTS commits ( + commit_id INTEGER PRIMARY KEY AUTOINCREMENT + ,repo_id INTEGER + ,commit_status VARCHAR(255) + ,commit_started INTEGER + ,commit_finished INTEGER + ,commit_duration INTEGER + ,commit_sha VARCHAR(255) + ,commit_branch VARCHAR(255) + ,commit_pr VARCHAR(255) + ,commit_author VARCHAR(255) + ,commit_gravatar VARCHAR(255) + ,commit_timestamp VARCHAR(255) + ,commit_message VARCHAR(255) + ,commit_yaml BLOB + ,commit_created INTEGER + ,commit_updated INTEGER + ,UNIQUE(commit_sha, commit_branch, repo_id) +); +` + +var commitRepoIndex = ` +CREATE INDEX commit_repo_id_idx ON commits (repo_id); +` + +var blobTable = ` +CREATE TABLE IF NOT EXISTS blobs ( + blob_id INTEGER PRIMARY KEY AUTOINCREMENT + ,blob_path VARCHAR(255) + ,blob_data BLOB + ,UNIQUE(blob_path) +); +` diff --git a/server/handler/hook.go b/server/handler/hook.go index 31f85852..f3b18663 100644 --- a/server/handler/hook.go +++ b/server/handler/hook.go @@ -1,6 +1,7 @@ package handler import ( + "log" "net/http" "strings" @@ -24,6 +25,7 @@ import ( func PostHook(c web.C, w http.ResponseWriter, r *http.Request) { var ctx = context.FromC(c) var host = c.URLParams["host"] + var token = r.FormValue("token") var remote = remote.Lookup(host) if remote == nil { w.WriteHeader(http.StatusNotFound) @@ -52,6 +54,14 @@ func PostHook(c web.C, w http.ResponseWriter, r *http.Request) { return } + // each hook contains a token to verify the sender. If the token + // is not provided or does not match, exit + if len(repo.Token) == 0 || repo.Token != token { + log.Printf("Rejected post commit hook for %s. Token mismatch\n", repo.Name) + w.WriteHeader(http.StatusUnauthorized) + return + } + if repo.Active == false || (repo.PostCommit == false && len(hook.PullRequest) == 0) || (repo.PullRequest == false && len(hook.PullRequest) != 0) { diff --git a/server/handler/repo.go b/server/handler/repo.go index 70478dcc..54896b9f 100644 --- a/server/handler/repo.go +++ b/server/handler/repo.go @@ -80,6 +80,7 @@ func PostRepo(c web.C, w http.ResponseWriter, r *http.Request) { repo.PostCommit = true repo.UserID = user.ID repo.Timeout = 3600 // default to 1 hour + repo.Token = model.GenerateToken() // generates the rsa key key, err := sshutil.GeneratePrivateKey() @@ -98,7 +99,7 @@ func PostRepo(c web.C, w http.ResponseWriter, r *http.Request) { // setup the post-commit hook with the remote system and // if necessary, register the public key - var hook = fmt.Sprintf("%s/api/hook/%s", httputil.GetURL(r), repo.Remote) + var hook = fmt.Sprintf("%s/api/hook/%s?token=%s", httputil.GetURL(r), repo.Remote, repo.Token) if err := remote.Activate(user, repo, hook); err != nil { w.WriteHeader(http.StatusInternalServerError) return diff --git a/shared/model/commit.go b/shared/model/commit.go index 7e4c5710..da8c0c65 100644 --- a/shared/model/commit.go +++ b/shared/model/commit.go @@ -26,7 +26,7 @@ type Commit struct { // SetAuthor sets the author's email address and calculate the Gravatar hash. func (c *Commit) SetAuthor(email string) { c.Author = email - c.Gravatar = createGravatar(email) + c.Gravatar = CreateGravatar(email) } // Returns the Short (--short) Commit Hash. diff --git a/shared/model/repo.go b/shared/model/repo.go index df31e2c1..4c7a208c 100644 --- a/shared/model/repo.go +++ b/shared/model/repo.go @@ -18,6 +18,7 @@ type RepoParams map[string]string type Repo struct { ID int64 `meddler:"repo_id,pk" json:"-"` UserID int64 `meddler:"user_id" json:"-"` + Token string `meddler:"repo_token" json:"-"` Remote string `meddler:"repo_remote" json:"remote"` Host string `meddler:"repo_host" json:"host"` Owner string `meddler:"repo_owner" json:"owner"` diff --git a/shared/model/user.go b/shared/model/user.go index c030ea62..5edc9455 100644 --- a/shared/model/user.go +++ b/shared/model/user.go @@ -24,7 +24,7 @@ type User struct { func NewUser(remote, login, email string) *User { user := User{} - user.Token = generateToken() + user.Token = GenerateToken() user.Login = login user.Remote = remote user.Active = true @@ -35,7 +35,7 @@ func NewUser(remote, login, email string) *User { // SetEmail sets the email address and calculate the Gravatar hash. func (u *User) SetEmail(email string) { u.Email = email - u.Gravatar = createGravatar(email) + u.Gravatar = CreateGravatar(email) } func (u *User) IsStale() bool { diff --git a/shared/model/util.go b/shared/model/util.go index d8c25262..23ca43a0 100644 --- a/shared/model/util.go +++ b/shared/model/util.go @@ -14,9 +14,9 @@ var chars = []byte("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567 // default token length var length = 40 -// generateToken generates random strings good for use in URIs to +// GenerateToken generates random strings good for use in URIs to // identify unique objects. -func generateToken() string { +func GenerateToken() string { b := make([]byte, length) r := make([]byte, length+(length/4)) // storage for random bytes. clen := byte(len(chars)) @@ -40,7 +40,7 @@ func generateToken() string { // helper function to create a Gravatar Hash // for the given Email address. -func createGravatar(email string) string { +func CreateGravatar(email string) string { email = strings.ToLower(strings.TrimSpace(email)) hash := md5.New() hash.Write([]byte(email)) diff --git a/shared/model/util_test.go b/shared/model/util_test.go index 4aa17166..07ad4afe 100644 --- a/shared/model/util_test.go +++ b/shared/model/util_test.go @@ -4,15 +4,15 @@ import ( "testing" ) -func Test_createGravatar(t *testing.T) { - var got, want = createGravatar("dr_cooper@caltech.edu"), "2b77ba83e2216ddcd11fe8c24b70c2a3" +func Test_CreateGravatar(t *testing.T) { + var got, want = CreateGravatar("dr_cooper@caltech.edu"), "2b77ba83e2216ddcd11fe8c24b70c2a3" if got != want { t.Errorf("Got gravatar hash %s, want %s", got, want) } } -func Test_generateToken(t *testing.T) { - token := generateToken() +func Test_GenerateToken(t *testing.T) { + token := GenerateToken() if len(token) != length { t.Errorf("Want token length %d, got %d", length, len(token)) }