mirror of
https://github.com/goreleaser/nfpm
synced 2024-05-22 08:06:17 +02:00
feat: add support for remote signing keys (#695)
* feat: Add support for remote signing keys When used as a library, `nfpm.PackageSignature.SignFn` can be set as an alternative to `KeyFile`. This allows arbitrary signing key implementations, like a remote signing server. Updates https://github.com/tailscale/tailscale/issues/1882 * Update rpm/rpm_test.go Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com> --------- Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
parent
577ae45531
commit
24a43c5ad7
12
apk/apk.go
12
apk/apk.go
|
@ -132,7 +132,7 @@ func (*Apk) Package(info *nfpm.Info, apk io.Writer) (err error) {
|
|||
return err
|
||||
}
|
||||
|
||||
if info.APK.Signature.KeyFile == "" {
|
||||
if info.APK.Signature.KeyFile == "" && info.APK.Signature.SignFn == nil {
|
||||
return combineToApk(apk, &bufControl, &bufData)
|
||||
}
|
||||
|
||||
|
@ -267,8 +267,14 @@ var errNoKeyAddress = errors.New("key name not set and maintainer mail address e
|
|||
|
||||
func createSignatureBuilder(digest []byte, info *nfpm.Info) func(*tar.Writer) error {
|
||||
return func(tw *tar.Writer) error {
|
||||
signature, err := sign.RSASignSHA1Digest(digest,
|
||||
info.APK.Signature.KeyFile, info.APK.Signature.KeyPassphrase)
|
||||
var signature []byte
|
||||
var err error
|
||||
if signFn := info.APK.Signature.SignFn; signFn != nil {
|
||||
signature, err = signFn(bytes.NewReader(digest))
|
||||
} else {
|
||||
signature, err = sign.RSASignSHA1Digest(digest,
|
||||
info.APK.Signature.KeyFile, info.APK.Signature.KeyPassphrase)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -363,6 +363,33 @@ func TestSignatureError(t *testing.T) {
|
|||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestSignatureCallback(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.APK.Signature.SignFn = func(r io.Reader) ([]byte, error) {
|
||||
digest, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sign.RSASignSHA1Digest(digest, "../internal/sign/testdata/rsa.priv", "hunter2")
|
||||
}
|
||||
info.APK.Signature.KeyName = "testkey.rsa.pub"
|
||||
err := nfpm.PrepareForPackager(info, "apk")
|
||||
require.NoError(t, err)
|
||||
|
||||
digest := sha1.New().Sum(nil) // nolint:gosec
|
||||
|
||||
var signatureTarGz bytes.Buffer
|
||||
tw := tar.NewWriter(&signatureTarGz)
|
||||
require.NoError(t, createSignatureBuilder(digest, info)(tw))
|
||||
|
||||
signature := extractFromTar(t, signatureTarGz.Bytes(), ".SIGN.RSA.testkey.rsa.pub")
|
||||
err = sign.RSAVerifySHA1Digest(digest, signature, "../internal/sign/testdata/rsa.pub")
|
||||
require.NoError(t, err)
|
||||
|
||||
err = Default.Package(info, io.Discard)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDisableGlobbing(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.DisableGlobbing = true
|
||||
|
|
17
deb/deb.go
17
deb/deb.go
|
@ -136,7 +136,7 @@ func (d *Deb) Package(info *nfpm.Info, deb io.Writer) (err error) { // nolint: f
|
|||
return fmt.Errorf("cannot add data.tar.gz to deb: %w", err)
|
||||
}
|
||||
|
||||
if info.Deb.Signature.KeyFile != "" {
|
||||
if info.Deb.Signature.KeyFile != "" || info.Deb.Signature.SignFn != nil {
|
||||
sig, sigType, err := doSign(info, debianBinary, controlTarGz, dataTarball)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -172,7 +172,12 @@ func dpkgSign(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) (
|
|||
return nil, sigType, &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
||||
sig, err := sign.PGPClearSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
var sig []byte
|
||||
if signFn := info.Deb.Signature.SignFn; signFn != nil {
|
||||
sig, err = signFn(data)
|
||||
} else {
|
||||
sig, err = sign.PGPClearSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, sigType, &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
@ -193,7 +198,13 @@ func debSign(info *nfpm.Info, debianBinary, controlTarGz, dataTarball []byte) ([
|
|||
}
|
||||
}
|
||||
|
||||
sig, err := sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
var sig []byte
|
||||
var err error
|
||||
if signFn := info.Deb.Signature.SignFn; signFn != nil {
|
||||
sig, err = signFn(data)
|
||||
} else {
|
||||
sig, err = sign.PGPArmoredDetachSignWithKeyID(data, info.Deb.Signature.KeyFile, info.Deb.Signature.KeyPassphrase, info.Deb.Signature.KeyID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, sigType, &nfpm.ErrSigningFailure{Err: err}
|
||||
}
|
||||
|
|
|
@ -960,6 +960,28 @@ func TestDebsigsSignatureError(t *testing.T) {
|
|||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestDebsigsSignatureCallback(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.SignFn = func(r io.Reader) ([]byte, error) {
|
||||
return sign.PGPArmoredDetachSignWithKeyID(r, "../internal/sign/testdata/privkey.asc", "hunter2", nil)
|
||||
}
|
||||
|
||||
var deb bytes.Buffer
|
||||
err := Default.Package(info, &deb)
|
||||
require.NoError(t, err)
|
||||
|
||||
debBinary := extractFileFromAr(t, deb.Bytes(), "debian-binary")
|
||||
controlTarGz := extractFileFromAr(t, deb.Bytes(), "control.tar.gz")
|
||||
dataTarball := extractFileFromAr(t, deb.Bytes(), findDataTarball(t, deb.Bytes()))
|
||||
signature := extractFileFromAr(t, deb.Bytes(), "_gpgorigin")
|
||||
|
||||
message := io.MultiReader(bytes.NewReader(debBinary),
|
||||
bytes.NewReader(controlTarGz), bytes.NewReader(dataTarball))
|
||||
|
||||
err = sign.PGPVerify(message, signature, "../internal/sign/testdata/pubkey.asc")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDpkgSigSignature(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.KeyFile = "../internal/sign/testdata/privkey.asc"
|
||||
|
@ -990,6 +1012,24 @@ func TestDpkgSigSignatureError(t *testing.T) {
|
|||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestDpkgSigSignatureCallback(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.Deb.Signature.SignFn = func(r io.Reader) ([]byte, error) {
|
||||
return sign.PGPClearSignWithKeyID(r, "../internal/sign/testdata/privkey.asc", "hunter2", nil)
|
||||
}
|
||||
info.Deb.Signature.Method = "dpkg-sig"
|
||||
info.Deb.Signature.Signer = "bob McRobert"
|
||||
|
||||
var deb bytes.Buffer
|
||||
err := Default.Package(info, &deb)
|
||||
require.NoError(t, err)
|
||||
|
||||
signature := extractFileFromAr(t, deb.Bytes(), "_gpgbuilder")
|
||||
|
||||
err = sign.PGPReadMessage(signature, "../internal/sign/testdata/pubkey.asc")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestDisableGlobbing(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.DisableGlobbing = true
|
||||
|
|
7
nfpm.go
7
nfpm.go
|
@ -351,6 +351,13 @@ type PackageSignature struct {
|
|||
KeyFile string `yaml:"key_file,omitempty" json:"key_file,omitempty" jsonschema:"title=key file,example=key.gpg"`
|
||||
KeyID *string `yaml:"key_id,omitempty" json:"key_id,omitempty" jsonschema:"title=key id,example=bc8acdd415bd80b3"`
|
||||
KeyPassphrase string `yaml:"-" json:"-"` // populated from environment variable
|
||||
// SignFn, if set, will be called with the package-specific data to sign.
|
||||
// For deb and rpm packages, data is the full package content.
|
||||
// For apk packages, data is the SHA1 digest of control tgz.
|
||||
//
|
||||
// This allows for signing implementations other than using a local file
|
||||
// (for example using a remote signer like KMS).
|
||||
SignFn func(data io.Reader) ([]byte, error) `yaml:"-" json:"-"` // populated when used as a library
|
||||
}
|
||||
|
||||
type RPMSignature struct {
|
||||
|
|
|
@ -126,6 +126,11 @@ func (*RPM) Package(info *nfpm.Info, w io.Writer) (err error) {
|
|||
info.RPM.Signature.KeyID,
|
||||
))
|
||||
}
|
||||
if signFn := info.RPM.Signature.SignFn; signFn != nil {
|
||||
rpm.SetPGPSigner(func(data []byte) ([]byte, error) {
|
||||
return signFn(bytes.NewReader(data))
|
||||
})
|
||||
}
|
||||
|
||||
if err = createFilesInsideRPM(info, rpm); err != nil {
|
||||
return err
|
||||
|
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/goreleaser/chglog"
|
||||
"github.com/goreleaser/nfpm/v2"
|
||||
"github.com/goreleaser/nfpm/v2/files"
|
||||
"github.com/goreleaser/nfpm/v2/internal/sign"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
@ -743,6 +744,32 @@ func TestRPMSignatureError(t *testing.T) {
|
|||
require.True(t, errors.As(err, &expectedError))
|
||||
}
|
||||
|
||||
func TestRPMSignatureCallback(t *testing.T) {
|
||||
info := exampleInfo()
|
||||
info.RPM.Signature.SignFn = func(r io.Reader) ([]byte, error) {
|
||||
data, err := io.ReadAll(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return sign.PGPSignerWithKeyID("../internal/sign/testdata/privkey.asc", "hunter2", nil)(data)
|
||||
}
|
||||
|
||||
pubkeyFileContent, err := os.ReadFile("../internal/sign/testdata/pubkey.gpg")
|
||||
require.NoError(t, err)
|
||||
|
||||
keyring, err := openpgp.ReadKeyRing(bytes.NewReader(pubkeyFileContent))
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, keyring, "cannot verify sigs with an empty keyring")
|
||||
|
||||
var rpmBuffer bytes.Buffer
|
||||
err = Default.Package(info, &rpmBuffer)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, sigs, err := rpmutils.Verify(bytes.NewReader(rpmBuffer.Bytes()), keyring)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sigs, 2)
|
||||
}
|
||||
|
||||
func TestRPMGhostFiles(t *testing.T) {
|
||||
filename := "/usr/lib/casper.a"
|
||||
|
||||
|
|
Loading…
Reference in New Issue