Skip to content

Commit

Permalink
feat(server): auto sign all release targets
Browse files Browse the repository at this point in the history
* automatically generate gpg signing key and store into the storage;
* get public part of gpg signing key with GET `/configure/gpg_signing_key`;
* delete current gpg signing key with DELETE `/configure/gpg_signing_key` (key will be automatically regenerated);
* each release target `targets/releases/RELEASE/OS-ARCH/FILE` have a signature
  available in the `targets/signatures/RELEASE/OS-ARCH/FILE.sig` file.
  • Loading branch information
distorhead committed Oct 28, 2021
1 parent 6ea22f5 commit c6221a8
Show file tree
Hide file tree
Showing 11 changed files with 431 additions and 24 deletions.
2 changes: 1 addition & 1 deletion e2e/go.mod
Expand Up @@ -6,7 +6,7 @@ require (
github.com/Masterminds/goutils v1.1.1
github.com/hashicorp/go-hclog v0.16.1 // indirect
github.com/hashicorp/vault/sdk v0.2.1
github.com/onsi/ginkgo v1.16.4
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.16.0
github.com/otiai10/copy v1.6.0
github.com/prashantv/gostub v1.0.0
Expand Down
3 changes: 2 additions & 1 deletion e2e/go.sum
Expand Up @@ -376,8 +376,9 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc=
github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0=
github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
Expand Down
8 changes: 4 additions & 4 deletions server/backend.go
Expand Up @@ -51,11 +51,11 @@ func Factory(ctx context.Context, conf *logical.BackendConfig) (logical.Backend,

func NewBackend(logger hclog.Logger) (*Backend, error) {
tasksManager := tasks_manager.NewManager(logger)
publisherManager := publisher.NewPublisher(logger)
publisher := publisher.NewPublisher(logger)

b := &Backend{
TasksManager: tasksManager,
Publisher: publisherManager,
Publisher: publisher,
}
b.BackendPeriodic = b

Expand All @@ -64,8 +64,8 @@ func NewBackend(logger hclog.Logger) (*Backend, error) {
Help: backendHelp,
}

b.InitPaths(tasksManager)
b.InitPeriodicFunc(tasksManager, publisherManager)
b.InitPaths(tasksManager, publisher)
b.InitPeriodicFunc(tasksManager, publisher)
return b, nil
}

Expand Down
4 changes: 3 additions & 1 deletion server/go.mod
Expand Up @@ -15,12 +15,14 @@ require (
github.com/hashicorp/go-hclog v0.16.1
github.com/hashicorp/vault/api v1.1.0
github.com/hashicorp/vault/sdk v0.2.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.16.0
github.com/satori/go.uuid v1.2.0
github.com/stretchr/testify v1.5.1
github.com/theupdateframework/go-tuf v0.0.0-20201230183259-aee6270feb55
github.com/werf/logboek v0.5.4
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
gopkg.in/yaml.v2 v2.3.0
gopkg.in/yaml.v2 v2.4.0
)

replace github.com/theupdateframework/go-tuf => github.com/werf/third-party-go-tuf v0.0.0-20210420212757-8e2932fb01f2
58 changes: 50 additions & 8 deletions server/go.sum

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion server/path_release.go
Expand Up @@ -131,7 +131,7 @@ func (b *Backend) pathRelease(ctx context.Context, req *logical.Request, fields
}

logboek.Context(ctx).Default().LogF("Verifying tag PGP signatures of the git tag %q\n", gitTag)
b.Logger().Debug("Verifying tag PGP signatures of the git tag %q", gitTag)
b.Logger().Debug(fmt.Sprintf("Verifying tag PGP signatures of the git tag %q", gitTag))

trustedPGPPublicKeys, err := pgp.GetTrustedPGPPublicKeys(ctx, req.Storage)
if err != nil {
Expand Down
87 changes: 87 additions & 0 deletions server/pkg/pgp/rsa_signing_key.go
@@ -0,0 +1,87 @@
package pgp

import (
"crypto"
"crypto/rand"
"fmt"
"io"
"time"

"golang.org/x/crypto/openpgp"
"golang.org/x/crypto/openpgp/armor"
"golang.org/x/crypto/openpgp/packet"
)

type RSASigningKey struct {
Entity *openpgp.Entity
}

func (key *RSASigningKey) SerializePublicKey(out io.Writer) error {
armoredOut, err := armor.Encode(out, openpgp.PublicKeyType, nil)
if err != nil {
return fmt.Errorf("unable to prepare armored writer: %s", err)
}

if err := key.Entity.Serialize(armoredOut); err != nil {
return err
}

if err := armoredOut.Close(); err != nil {
return fmt.Errorf("unable to close armored writer: %s", err)
}

return nil
}

func (key *RSASigningKey) SerializeFull(out io.Writer) error {
return key.SerializePrivateKey(out)
}

func (key *RSASigningKey) SerializePrivateKey(out io.Writer) error {
armoredOut, err := armor.Encode(out, openpgp.PrivateKeyType, nil)
if err != nil {
return fmt.Errorf("unable to prepare armored writer: %s", err)
}

if err := key.Entity.SerializePrivate(armoredOut, nil); err != nil {
return err
}

if err := armoredOut.Close(); err != nil {
return fmt.Errorf("unable to close armored writer: %s", err)
}

return nil
}

func GenerateRSASigningKey() (*RSASigningKey, error) {
entity, err := openpgp.NewEntity("trdl", "trdl server auto signer", "", &packet.Config{
Time: time.Now,
Rand: rand.Reader,
DefaultHash: crypto.SHA256,
DefaultCipher: packet.CipherAES128,
RSABits: 4096,
})
if err != nil {
return nil, fmt.Errorf("unable to generate openpgp entity: %s", err)
}

return &RSASigningKey{Entity: entity}, nil
}

func ParseRSASigningKey(in io.Reader) (*RSASigningKey, error) {
el, err := openpgp.ReadArmoredKeyRing(in)
if err != nil {
return nil, err
}

if len(el) == 0 {
return nil, fmt.Errorf("no private pgp signing key entities found")
}

return &RSASigningKey{Entity: el[0]}, nil
}

func SignDataStream(detachedSignatureOut io.Writer, dataStream io.Reader, key *RSASigningKey) error {
return openpgp.DetachSign(detachedSignatureOut, key.Entity, dataStream, nil)
}
110 changes: 110 additions & 0 deletions server/pkg/pgp/rsa_signing_key_test.go
@@ -0,0 +1,110 @@
package pgp

import (
"bytes"
"fmt"
"io"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"testing"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func TestPGPSigningKey(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "PGP signing key suite")
}

var _ = Describe("PGP signing key", func() {
BeforeEach(func() {
rand.Seed(time.Now().Unix())
})

It("Should create detached signature of data stream then decode signature using GPG tool", func() {
key, err := GenerateRSASigningKey()
Expect(err).NotTo(HaveOccurred())

fileContent := make([]byte, rand.Uint32()%104857600)
rand.Read(fileContent)

pgpSignBuf := bytes.NewBuffer(nil)

err = SignDataStream(pgpSignBuf, bytes.NewReader(fileContent), key)
Expect(err).NotTo(HaveOccurred())

pgpSign := pgpSignBuf.Bytes()

Expect(len(pgpSignBuf.String()) > 0).To(BeTrue())

tmpFile, err := ioutil.TempFile("", "pgp-signing-key-test-file-")
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(tmpFile.Name())
_, err = io.Copy(tmpFile, bytes.NewReader(fileContent))
Expect(err).NotTo(HaveOccurred())
err = tmpFile.Close()
Expect(err).NotTo(HaveOccurred())

tmpSigFile, err := ioutil.TempFile("", "pgp-signing-key-test-file-*.sig")
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(tmpSigFile.Name())
_, err = io.Copy(tmpSigFile, bytes.NewReader(pgpSign))
Expect(err).NotTo(HaveOccurred())
err = tmpSigFile.Close()
Expect(err).NotTo(HaveOccurred())

tmpPubkeyFile, err := ioutil.TempFile("", "pgp-signing-key-test-pubkey-*.gpg")
Expect(err).NotTo(HaveOccurred())
defer os.RemoveAll(tmpPubkeyFile.Name())
pubkeyData := bytes.NewBuffer(nil)
err = key.SerializePublicKey(pubkeyData)
Expect(err).NotTo(HaveOccurred())
_, err = io.Copy(tmpPubkeyFile, bytes.NewReader(pubkeyData.Bytes()))
Expect(err).NotTo(HaveOccurred())
err = tmpPubkeyFile.Close()
Expect(err).NotTo(HaveOccurred())

fmt.Printf("Importing gpg key:\n%s\n", pubkeyData)

cmd := exec.Command("gpg", "--import", tmpPubkeyFile.Name())
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())

cmd = exec.Command("gpg", "--verify", tmpSigFile.Name(), tmpFile.Name())
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = cmd.Run()
Expect(err).NotTo(HaveOccurred())
})

It("Should serialize and deserialize PGP signing key into text", func() {
key, err := GenerateRSASigningKey()
Expect(err).NotTo(HaveOccurred())

data := bytes.NewBuffer(nil)
err = key.SerializeFull(data)
Expect(err).NotTo(HaveOccurred())
fmt.Printf("Serialized key:\n%s\n", data.String())

newKey, err := ParseRSASigningKey(bytes.NewReader(data.Bytes()))
Expect(err).NotTo(HaveOccurred())

Expect(key.Entity.PrimaryKey.KeyId).To(Equal(newKey.Entity.PrimaryKey.KeyId))
Expect(key.Entity.PrimaryKey.Fingerprint).To(Equal(newKey.Entity.PrimaryKey.Fingerprint))

Expect(key.Entity.PrivateKey.KeyId).To(Equal(newKey.Entity.PrivateKey.KeyId))
Expect(key.Entity.PrivateKey.Fingerprint).To(Equal(newKey.Entity.PrimaryKey.Fingerprint))

for k, ident := range key.Entity.Identities {
newIdent := newKey.Entity.Identities[k]
Expect(ident.UserId).To(Equal(newIdent.UserId))
Expect(ident.Name).To(Equal(newIdent.Name))
}
})
})
56 changes: 53 additions & 3 deletions server/pkg/publisher/backend.go
@@ -1,7 +1,57 @@
package publisher

import "github.com/hashicorp/vault/sdk/framework"
import (
"bytes"
"context"
"fmt"

func (m *Publisher) Paths() []*framework.Path {
return nil
"github.com/hashicorp/vault/sdk/framework"
"github.com/hashicorp/vault/sdk/logical"
)

func (publisher *Publisher) Paths() []*framework.Path {
return []*framework.Path{
{
Pattern: "configure/pgp_signing_key",
Fields: map[string]*framework.FieldSchema{},
Operations: map[logical.Operation]framework.OperationHandler{
logical.ReadOperation: &framework.PathOperation{
Description: "Get public part of PGP signing key",
Callback: publisher.pathConfigurePGPSigningKeyRead,
},
logical.DeleteOperation: &framework.PathOperation{
Description: "Delete current PGP signing key (new key will be generated automatically ondemand)",
Callback: publisher.pathConfigurePGPSigningKeyDelete,
},
},
},
}
}

func (publisher *Publisher) pathConfigurePGPSigningKeyRead(ctx context.Context, req *logical.Request, fields *framework.FieldData) (*logical.Response, error) {
key, err := publisher.fetchPGPSigningKey(ctx, req.Storage, false)
if err == ErrUninitializedPGPSigningKey {
return logical.ErrorResponse(ErrUninitializedPGPSigningKey.Error()), nil
}
if err != nil {
return nil, fmt.Errorf("error fetching pgp signing key: %s", err)
}

pk := bytes.NewBuffer(nil)
if err := key.SerializePublicKey(pk); err != nil {
return nil, fmt.Errorf("unable to get public key text: %s", err)
}

return &logical.Response{
Data: map[string]interface{}{
"public_key": pk.String(),
},
}, nil
}

func (publisher *Publisher) pathConfigurePGPSigningKeyDelete(ctx context.Context, req *logical.Request, fields *framework.FieldData) (*logical.Response, error) {
if err := publisher.deletePGPSigningKey(ctx, req.Storage); err != nil {
return nil, fmt.Errorf("error deleting pgp signing key: %s", err)
}
return &logical.Response{Data: map[string]interface{}{}}, nil
}

0 comments on commit c6221a8

Please sign in to comment.