1
0
mirror of https://github.com/drone/drone-cli.git synced 2024-11-23 17:22:09 +01:00
drone-cli/drone/secure.go
2016-02-17 13:33:59 -07:00

162 lines
3.6 KiB
Go

package main
import (
"crypto/rsa"
"crypto/sha256"
"fmt"
"io"
"io/ioutil"
"reflect"
"os"
"github.com/codegangsta/cli"
"github.com/drone/drone-exec/yaml/secure"
"github.com/drone/drone-go/drone"
"github.com/square/go-jose"
"golang.org/x/crypto/ssh"
"gopkg.in/yaml.v2"
)
var SecureCmd = cli.Command{
Name: "secure",
Usage: "creates a secure yaml file",
Action: func(c *cli.Context) {
handle(c, SecureYamlCmd)
},
Flags: []cli.Flag{
cli.StringFlag{
Name: "in",
Usage: "input path to the plaintext secret file (use - for stdin)",
Value: ".drone.sec.yml",
},
cli.StringFlag{
Name: "out",
Usage: "output path for the encrypted secret file (use - for stdout)",
Value: ".drone.sec",
},
cli.StringFlag{
Name: "repo",
Usage: "name of the repository",
},
cli.StringFlag{
Name: "yaml",
Usage: "path to .drone.yml file",
Value: ".drone.yml",
},
cli.BoolTFlag{
Name: "checksum",
Usage: "calculate and encrypt the yaml checksum",
},
},
}
func SecureYamlCmd(c *cli.Context, client drone.Client) error {
var (
repo = c.String("repo")
inFile = c.String("in")
outFile = c.String("out")
ymlFile = c.String("yaml")
checksum = c.BoolT("checksum")
)
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
keypair, err := client.RepoKey(owner, name)
if err != nil {
return err
}
key, err := toPublicKey(keypair.Public)
if err != nil {
return err
}
// read the .drone.sec.yml file (plain text)
plaintext, err := readInput(inFile)
if err != nil {
return err
}
// parse the .drone.sec.yml file
sec := new(secure.Secure)
err = yaml.Unmarshal(plaintext, sec)
if err != nil {
return err
}
// read the .drone.yml file and caclulate the
// checksum. add to the .drone.sec.yml file.
yml, err := ioutil.ReadFile(ymlFile)
if err == nil && checksum {
sec.Checksum = sha256sum(string(yml))
}
// re-marshal the .drone.sec.yml file since we've
// added the checksum
plaintext, err = yaml.Marshal(sec)
if err != nil {
return err
}
// encrypt the .drone.sec.yml file
ciphertext, err := encrypt(plaintext, key)
if err != nil {
return err
}
// write the encrypted .drone.sec.yml file to .drone.sec
return writeOutput(outFile, ciphertext)
}
// toPublicKey parses a public key and returns an *rsa.PublicKey.
// credit to stackoverflow http://stackoverflow.com/q/31593329
func toPublicKey(key string) (*rsa.PublicKey, error) {
raw := []byte(key)
pub, _, _, _, err := ssh.ParseAuthorizedKey(raw)
if err != nil {
return nil, err
}
return reflect.ValueOf(pub).
Convert(reflect.TypeOf(new(rsa.PublicKey))).Interface().(*rsa.PublicKey), nil
}
// encrypt encrypts a plaintext variable using JOSE with
// RSA_OAEP and A128GCM algorithms.
func encrypt(plaintext []byte, pubKey *rsa.PublicKey) (string, error) {
var encrypted string
// Creates a new encrypter using defaults
encrypter, err := jose.NewEncrypter(jose.RSA_OAEP, jose.A128GCM, pubKey)
if err != nil {
return encrypted, err
}
// Encrypts the plaintext value and serializes
// as a JOSE string.
object, err := encrypter.Encrypt(plaintext)
if err != nil {
return encrypted, err
}
return object.CompactSerialize()
}
func sha256sum(in string) string {
h := sha256.New()
io.WriteString(h, in)
return fmt.Sprintf("%x", h.Sum(nil))
}
// writeOutput writes the encrypted secret to a file
// or stdout if outFile is -
func writeOutput(outFile string, ciphertext string) error {
if outFile == "-" {
_, err := os.Stdout.Write([]byte(ciphertext))
return err
} else {
return ioutil.WriteFile(outFile, []byte(ciphertext), 0664)
}
}