support for .drone.sec file

This commit is contained in:
Brad Rydzewski 2015-09-07 12:13:27 -07:00
parent d4c7e7bf59
commit b65d1cda97
13 changed files with 80 additions and 125 deletions

View file

@ -119,9 +119,8 @@
$scope.encrypt = function (plaintext) { $scope.encrypt = function (plaintext) {
var data = {"DATA": plaintext}; repos.encrypt(fullName, plaintext).then(function (payload) {
repos.encrypt(fullName, data).then(function (payload) { $scope.secure = payload.data;
$scope.secure = payload.data["DATA"];
}).catch(function (err) { }).catch(function (err) {
$scope.error = err; $scope.error = err;
}); });

View file

@ -77,10 +77,10 @@
* Encrypt the set of parameters. * Encrypt the set of parameters.
* *
* @param {string} Name of the repository. * @param {string} Name of the repository.
* @param {object} Key/Value map of parameters. * @param {string} Plaintext to encrypt.
*/ */
this.encrypt = function (repoName, params) { this.encrypt = function (repoName, plaintext) {
return $http.post('/api/repos/' + repoName + '/encrypt', params); return $http.post('/api/repos/' + repoName + '/encrypt', plaintext);
}; };
var callback, var callback,

View file

@ -2,7 +2,7 @@
<article> <article>
<p style=" font-size: 16px; <p style=" font-size: 16px;
margin-bottom: 10px;">Encrypt and store secret variables in your <code>.drone.yml</code> file<p> margin-bottom: 10px;">Encrypt and store secret variables in the <code>.drone.sec</code> file<p>
<textarea spellcheck="false" placeholder="Enter a plaintext value here" ng-model="plaintext" style=" <textarea spellcheck="false" placeholder="Enter a plaintext value here" ng-model="plaintext" style="
background-color: #eff1f5; background-color: #eff1f5;
border-radius:3px; border-radius:3px;

View file

@ -15,7 +15,8 @@ type Work struct {
Build *common.Build `json:"build"` Build *common.Build `json:"build"`
Keys *common.Keypair `json:"keypair"` Keys *common.Keypair `json:"keypair"`
Netrc *common.Netrc `json:"netrc"` Netrc *common.Netrc `json:"netrc"`
Yaml []byte `json:"yaml"` Config []byte `json:"config"`
Secret []byte `json:"secret"`
} }
// represents a worker that has connected // represents a worker that has connected

View file

@ -163,9 +163,12 @@ func (g *GitHub) Perm(u *common.User, owner, name string) (*common.Perm, error)
// Script fetches the build script (.drone.yml) from the remote // Script fetches the build script (.drone.yml) from the remote
// repository and returns in string format. // repository and returns in string format.
func (g *GitHub) Script(u *common.User, r *common.Repo, b *common.Build) ([]byte, error) { func (g *GitHub) Script(u *common.User, r *common.Repo, b *common.Build) ([]byte, []byte, error) {
client := NewClient(g.API, u.Token, g.SkipVerify) client := NewClient(g.API, u.Token, g.SkipVerify)
return GetFile(client, r.Owner, r.Name, ".drone.yml", b.Commit.Sha)
cfg, err := GetFile(client, r.Owner, r.Name, ".drone.yml", b.Commit.Sha)
sec, _ := GetFile(client, r.Owner, r.Name, ".drone.sec", b.Commit.Sha)
return cfg, sec, err
} }
// Netrc returns a .netrc file that can be used to clone // Netrc returns a .netrc file that can be used to clone

View file

@ -159,14 +159,16 @@ func (g *Gitlab) Perm(u *common.User, owner, name string) (*common.Perm, error)
// GetScript fetches the build script (.drone.yml) from the remote // GetScript fetches the build script (.drone.yml) from the remote
// repository and returns in string format. // repository and returns in string format.
func (g *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Build) ([]byte, error) { func (g *Gitlab) Script(user *common.User, repo *common.Repo, build *common.Build) ([]byte, []byte, error) {
var client = NewClient(g.URL, user.Token, g.SkipVerify) var client = NewClient(g.URL, user.Token, g.SkipVerify)
id, err := GetProjectId(g, client, repo.Owner, repo.Name) id, err := GetProjectId(g, client, repo.Owner, repo.Name)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
return client.RepoRawFile(id, build.Commit.Sha, ".drone.yml") cfg, err := client.RepoRawFile(id, build.Commit.Sha, ".drone.yml")
enc, _ := client.RepoRawFile(id, build.Commit.Sha, ".drone.sec")
return cfg, enc, err
} }
// NOTE Currently gitlab doesn't support status for commits and events, // NOTE Currently gitlab doesn't support status for commits and events,

View file

@ -56,7 +56,7 @@ type Remote interface {
// Script fetches the build script (.drone.yml) from the remote // Script fetches the build script (.drone.yml) from the remote
// repository and returns in string format. // repository and returns in string format.
Script(u *types.User, r *types.Repo, b *types.Build) ([]byte, error) Script(u *types.User, r *types.Repo, b *types.Build) ([]byte, []byte, error)
// Status sends the commit status to the remote system. // Status sends the commit status to the remote system.
// An example would be the GitHub pull request status. // An example would be the GitHub pull request status.

View file

@ -144,7 +144,8 @@ func (r *Runner) Run(w *queue.Work) error {
Repo: w.Repo, Repo: w.Repo,
Build: w.Build, Build: w.Build,
Job: job, Job: job,
Yaml: string(w.Yaml), Secret: string(w.Secret),
Config: string(w.Config),
} }
in, err := json.Marshal(work) in, err := json.Marshal(work)
if err != nil { if err != nil {
@ -224,7 +225,8 @@ func (r *Runner) Run(w *queue.Work) error {
Repo: w.Repo, Repo: w.Repo,
Build: w.Build, Build: w.Build,
Job: job, Job: job,
Yaml: string(w.Yaml), Secret: string(w.Secret),
Config: string(w.Config),
} }
in, err := json.Marshal(work) in, err := json.Marshal(work)
if err != nil { if err != nil {

View file

@ -50,7 +50,8 @@ type work struct {
Job *types.Job `json:"job"` Job *types.Job `json:"job"`
System *types.System `json:"system"` System *types.System `json:"system"`
Workspace *types.Workspace `json:"workspace"` Workspace *types.Workspace `json:"workspace"`
Yaml string `json:"yaml"` Secret string `json:"secret"`
Config string `json:"config"`
} }
type worker struct { type worker struct {

View file

@ -6,14 +6,10 @@ import (
"strconv" "strconv"
"time" "time"
log "github.com/drone/drone/Godeps/_workspace/src/github.com/Sirupsen/logrus"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/pkg/queue" "github.com/drone/drone/pkg/queue"
common "github.com/drone/drone/pkg/types" common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil" "github.com/drone/drone/pkg/utils/httputil"
"github.com/drone/drone/pkg/yaml/inject"
"github.com/drone/drone/pkg/yaml/secure"
// "github.com/gin-gonic/gin/binding"
) )
// GetCommit accepts a request to retrieve a commit // GetCommit accepts a request to retrieve a commit
@ -175,24 +171,12 @@ func RunBuild(c *gin.Context) {
} }
// featch the .drone.yml file from the database // featch the .drone.yml file from the database
raw, err := remote.Script(user, repo, build) raw, sec, err := remote.Script(user, repo, build)
if err != nil { if err != nil {
c.Fail(404, err) c.Fail(404, err)
return return
} }
// inject any private parameters into the .drone.yml
if repo.Params != nil && len(repo.Params) != 0 {
raw = []byte(inject.InjectSafe(string(raw), repo.Params))
}
encrypted, err := secure.Parse(repo.Keys.Private, repo.Hash, string(raw))
if err != nil {
log.Errorf("failure to decrypt secure parameters for %s. %s", repo.FullName, err)
}
if encrypted != nil && len(encrypted) != 0 {
raw = []byte(inject.InjectSafe(string(raw), encrypted))
}
c.JSON(202, build) c.JSON(202, build)
queue_.Publish(&queue.Work{ queue_.Publish(&queue.Work{
@ -201,7 +185,8 @@ func RunBuild(c *gin.Context) {
Build: build, Build: build,
Keys: repo.Keys, Keys: repo.Keys,
Netrc: netrc, Netrc: netrc,
Yaml: raw, Config: raw,
Secret: sec,
System: &common.System{ System: &common.System{
Link: httputil.GetURL(c.Request), Link: httputil.GetURL(c.Request),
Plugins: conf.Plugins, Plugins: conf.Plugins,

View file

@ -10,9 +10,7 @@ import (
common "github.com/drone/drone/pkg/types" common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil" "github.com/drone/drone/pkg/utils/httputil"
"github.com/drone/drone/pkg/yaml" "github.com/drone/drone/pkg/yaml"
"github.com/drone/drone/pkg/yaml/inject"
"github.com/drone/drone/pkg/yaml/matrix" "github.com/drone/drone/pkg/yaml/matrix"
"github.com/drone/drone/pkg/yaml/secure"
) )
// PostHook accepts a post-commit hook and parses the payload // PostHook accepts a post-commit hook and parses the payload
@ -93,25 +91,13 @@ func PostHook(c *gin.Context) {
build.RepoID = repo.ID build.RepoID = repo.ID
// fetch the .drone.yml file from the database // fetch the .drone.yml file from the database
raw, err := remote.Script(user, repo, build) raw, sec, err := remote.Script(user, repo, build)
if err != nil { if err != nil {
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err) log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
c.Fail(404, err) c.Fail(404, err)
return return
} }
// inject any private parameters into the .drone.yml
if repo.Params != nil && len(repo.Params) != 0 {
raw = []byte(inject.InjectSafe(string(raw), repo.Params))
}
encrypted, err := secure.Parse(repo.Keys.Private, repo.Hash, string(raw))
if err != nil {
log.Errorf("failure to decrypt secure parameters for %s. %s", repo.FullName, err)
c.Fail(400, err)
return
}
if encrypted != nil && len(encrypted) != 0 {
raw = []byte(inject.InjectSafe(string(raw), encrypted))
}
axes, err := matrix.Parse(string(raw)) axes, err := matrix.Parse(string(raw))
if err != nil { if err != nil {
log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err) log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err)
@ -165,7 +151,8 @@ func PostHook(c *gin.Context) {
Build: build, Build: build,
Keys: repo.Keys, Keys: repo.Keys,
Netrc: netrc, Netrc: netrc,
Yaml: raw, Config: raw,
Secret: sec,
System: &common.System{ System: &common.System{
Link: httputil.GetURL(c.Request), Link: httputil.GetURL(c.Request),
Plugins: conf.Plugins, Plugins: conf.Plugins,

View file

@ -1,8 +1,8 @@
package server package server
import ( import (
"encoding/json"
"fmt" "fmt"
"io/ioutil"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin"
"github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding" "github.com/drone/drone/Godeps/_workspace/src/github.com/gin-gonic/gin/binding"
@ -249,16 +249,17 @@ func PostRepo(c *gin.Context) {
// //
func Encrypt(c *gin.Context) { func Encrypt(c *gin.Context) {
repo := ToRepo(c) repo := ToRepo(c)
in, err := ioutil.ReadAll(c.Request.Body)
in := map[string]string{}
json.NewDecoder(c.Request.Body).Decode(&in)
privKey := sshutil.UnMarshalPrivateKey([]byte(repo.Keys.Private))
err := secure.EncryptMap(secure.ToHash(repo.Hash), &privKey.PublicKey, in)
if err != nil { if err != nil {
c.Fail(500, err) c.Fail(500, err)
return return
} }
c.JSON(200, &in) out, err := secure.Encrypt(string(in), repo.Keys.Private)
if err != nil {
c.Fail(500, err)
return
}
c.Writer.Write([]byte(out))
} }
// Unsubscribe accapets a request to unsubscribe the // Unsubscribe accapets a request to unsubscribe the

View file

@ -2,71 +2,45 @@ package secure
import ( import (
"crypto/rsa" "crypto/rsa"
"crypto/sha256" "crypto/x509"
"hash" "encoding/pem"
"github.com/drone/drone/Godeps/_workspace/src/gopkg.in/yaml.v2" "github.com/square/go-jose"
"github.com/drone/drone/pkg/utils/sshutil"
) )
// Parse parses and returns the secure section of the // Encrypt encrypts a secret string.
// yaml file as plaintext parameters. func Encrypt(in, privKey string) (string, error) {
func Parse(privateKeyPEM, repoHash, raw string) (map[string]string, error) { rsaPrivKey, err := decodePrivateKey(privKey)
params, err := parseSecure(raw)
if err != nil { if err != nil {
return nil, err return "", err
} }
hasher := ToHash(repoHash) return encrypt(in, &rsaPrivKey.PublicKey)
privKey := sshutil.UnMarshalPrivateKey([]byte(privateKeyPEM))
err = DecryptMap(hasher, privKey, params)
return params, err
} }
// DecryptMap decrypts values of a map of named parameters // decodePrivateKey is a helper function that unmarshals a PEM
// from base64 to decrypted strings. // bytes to an RSA Private Key
func DecryptMap(hasher hash.Hash, privKey *rsa.PrivateKey, params map[string]string) error { func decodePrivateKey(privateKey string) (*rsa.PrivateKey, error) {
var err error derBlock, _ := pem.Decode([]byte(privateKey))
return x509.ParsePKCS1PrivateKey(derBlock.Bytes)
}
for name, encrypted := range params { // encrypt encrypts a plaintext variable using JOSE with
params[name], err = sshutil.Decrypt(hasher, privKey, encrypted) // RSA_OAEP and A128GCM algorithms.
func encrypt(text string, pubKey *rsa.PublicKey) (string, error) {
var encrypted string
var plaintext = []byte(text)
// Creates a new encrypter using defaults
encrypter, err := jose.NewEncrypter(jose.RSA_OAEP, jose.A128GCM, pubKey)
if err != nil { if err != nil {
return err return encrypted, err
} }
} // Encrypts the plaintext value and serializes
return nil // as a JOSE string.
} object, err := encrypter.Encrypt(plaintext)
// EncryptMap encrypts values of a map of named parameters
func EncryptMap(hasher hash.Hash, pubKey *rsa.PublicKey, params map[string]string) error {
var err error
for name, value := range params {
params[name], err = sshutil.Encrypt(hasher, pubKey, value)
if err != nil { if err != nil {
return err return encrypted, err
} }
} return object.CompactSerialize()
return nil
}
// parseSecure is helper function to parse the Secure data from
// the raw yaml file.
func parseSecure(raw string) (map[string]string, error) {
data := struct {
Secure map[string]string
}{}
err := yaml.Unmarshal([]byte(raw), &data)
return data.Secure, err
}
// ToHash is helper function to generate Hash of given string
func ToHash(key string) hash.Hash {
hasher := sha256.New()
hasher.Write([]byte(key))
hasher.Reset()
return hasher
} }