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) {
var data = {"DATA": plaintext};
repos.encrypt(fullName, data).then(function (payload) {
$scope.secure = payload.data["DATA"];
repos.encrypt(fullName, plaintext).then(function (payload) {
$scope.secure = payload.data;
}).catch(function (err) {
$scope.error = err;
});

View file

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

View file

@ -2,7 +2,7 @@
<article>
<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="
background-color: #eff1f5;
border-radius:3px;

View file

@ -15,7 +15,8 @@ type Work struct {
Build *common.Build `json:"build"`
Keys *common.Keypair `json:"keypair"`
Netrc *common.Netrc `json:"netrc"`
Yaml []byte `json:"yaml"`
Config []byte `json:"config"`
Secret []byte `json:"secret"`
}
// 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
// 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)
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

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
// 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)
id, err := GetProjectId(g, client, repo.Owner, repo.Name)
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,

View file

@ -56,7 +56,7 @@ type Remote interface {
// Script fetches the build script (.drone.yml) from the remote
// 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.
// 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,
Build: w.Build,
Job: job,
Yaml: string(w.Yaml),
Secret: string(w.Secret),
Config: string(w.Config),
}
in, err := json.Marshal(work)
if err != nil {
@ -224,7 +225,8 @@ func (r *Runner) Run(w *queue.Work) error {
Repo: w.Repo,
Build: w.Build,
Job: job,
Yaml: string(w.Yaml),
Secret: string(w.Secret),
Config: string(w.Config),
}
in, err := json.Marshal(work)
if err != nil {

View file

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

View file

@ -6,14 +6,10 @@ import (
"strconv"
"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/pkg/queue"
common "github.com/drone/drone/pkg/types"
"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
@ -175,24 +171,12 @@ func RunBuild(c *gin.Context) {
}
// 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 {
c.Fail(404, err)
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)
queue_.Publish(&queue.Work{
@ -201,7 +185,8 @@ func RunBuild(c *gin.Context) {
Build: build,
Keys: repo.Keys,
Netrc: netrc,
Yaml: raw,
Config: raw,
Secret: sec,
System: &common.System{
Link: httputil.GetURL(c.Request),
Plugins: conf.Plugins,

View file

@ -10,9 +10,7 @@ import (
common "github.com/drone/drone/pkg/types"
"github.com/drone/drone/pkg/utils/httputil"
"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/secure"
)
// PostHook accepts a post-commit hook and parses the payload
@ -93,25 +91,13 @@ func PostHook(c *gin.Context) {
build.RepoID = repo.ID
// 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 {
log.Errorf("failure to get .drone.yml for %s. %s", repo.FullName, err)
c.Fail(404, err)
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))
if err != nil {
log.Errorf("failure to calculate matrix for %s. %s", repo.FullName, err)
@ -165,7 +151,8 @@ func PostHook(c *gin.Context) {
Build: build,
Keys: repo.Keys,
Netrc: netrc,
Yaml: raw,
Config: raw,
Secret: sec,
System: &common.System{
Link: httputil.GetURL(c.Request),
Plugins: conf.Plugins,

View file

@ -1,8 +1,8 @@
package server
import (
"encoding/json"
"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/binding"
@ -249,16 +249,17 @@ func PostRepo(c *gin.Context) {
//
func Encrypt(c *gin.Context) {
repo := ToRepo(c)
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)
in, err := ioutil.ReadAll(c.Request.Body)
if err != nil {
c.Fail(500, err)
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

View file

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