mirror of
https://github.com/woodpecker-ci/woodpecker.git
synced 2024-12-23 00:46:30 +00:00
support for .drone.sec file
This commit is contained in:
parent
d4c7e7bf59
commit
b65d1cda97
13 changed files with 80 additions and 125 deletions
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue