1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-03-17 20:47:50 +02:00

feat: sign: extra option allowing stdin for commands (#1770)

* feat: extra option allowing stdin for the signing command

Signed-off-by: Yoan Blanc <yoan@dosimple.ch>

* feat: allow stdin to be an empty string

Signed-off-by: Yoan Blanc <yoan@dosimple.ch>

* fix: increase code coverage

Signed-off-by: Yoan Blanc <yoan@dosimple.ch>
This commit is contained in:
Yoan Blanc 2020-10-02 21:26:37 +02:00 committed by GitHub
parent e98aa8bc87
commit 468387625b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 154 additions and 6 deletions

View File

@ -2,9 +2,11 @@ package sign
import (
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/apex/log"
"github.com/goreleaser/goreleaser/internal/artifact"
@ -120,6 +122,19 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
args = append(args, arg)
}
var stdin io.Reader
if cfg.Stdin != nil {
stdin = strings.NewReader(*cfg.Stdin)
} else if cfg.StdinFile != "" {
f, err := os.Open(cfg.StdinFile)
if err != nil {
return nil, errors.Wrapf(err, "sign failed: cannot open file %s", cfg.StdinFile)
}
defer f.Close()
stdin = f
}
// The GoASTScanner flags this as a security risk.
// However, this works as intended. The nosec annotation
// tells the scanner to ignore this.
@ -127,6 +142,9 @@ func signone(ctx *context.Context, cfg config.Sign, a *artifact.Artifact) (*arti
cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
cmd.Stderr = logext.NewWriter(log.WithField("cmd", cfg.Cmd))
cmd.Stdout = cmd.Stderr
if stdin != nil {
cmd.Stdin = stdin
}
log.WithField("cmd", cmd.Args).Info("signing")
if err := cmd.Run(); err != nil {
return nil, fmt.Errorf("sign: %s failed", cfg.Cmd)

View File

@ -23,6 +23,9 @@ import (
var originKeyring = "testdata/gnupg"
var keyring string
const user = "nopass"
const passwordUser = "password"
func TestMain(m *testing.M) {
rand.Seed(time.Now().UnixNano())
keyring = fmt.Sprintf("/tmp/gorel_gpg_test.%d", rand.Int())
@ -31,6 +34,7 @@ func TestMain(m *testing.M) {
fmt.Printf("failed to copy %s to %s: %s", originKeyring, keyring, err)
os.Exit(1)
}
defer os.RemoveAll(keyring)
os.Exit(m.Run())
}
@ -79,12 +83,14 @@ func TestSignInvalidArtifacts(t *testing.T) {
}
func TestSignArtifacts(t *testing.T) {
stdin := passwordUser
tests := []struct {
desc string
ctx *context.Context
signaturePaths []string
signatureNames []string
expectedErrMsg string
user string
}{
{
desc: "sign errors",
@ -283,18 +289,99 @@ func TestSignArtifacts(t *testing.T) {
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig"},
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig"},
},
{
desc: "sign single with password from stdin",
ctx: context.New(
config.Project{
Signs: []config.Sign{
{
Artifacts: "all",
Args: []string{
"-u",
passwordUser,
"--batch",
"--pinentry-mode",
"loopback",
"--passphrase-fd",
"0",
"--output",
"${signature}",
"--detach-sign",
"${artifact}",
},
Stdin: &stdin,
},
},
},
),
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig"},
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig"},
user: passwordUser,
},
{
desc: "sign single with password from stdin_file",
ctx: context.New(
config.Project{
Signs: []config.Sign{
{
Artifacts: "all",
Args: []string{
"-u",
passwordUser,
"--batch",
"--pinentry-mode",
"loopback",
"--passphrase-fd",
"0",
"--output",
"${signature}",
"--detach-sign",
"${artifact}",
},
StdinFile: filepath.Join(keyring, passwordUser),
},
},
},
),
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig"},
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig"},
user: passwordUser,
},
{
desc: "missing stdin_file",
ctx: context.New(
config.Project{
Signs: []config.Sign{
{
Artifacts: "all",
Args: []string{
"--batch",
"--pinentry-mode",
"loopback",
"--passphrase-fd",
"0",
},
StdinFile: "/tmp/non-existing-file",
},
},
},
),
expectedErrMsg: `sign failed: cannot open file /tmp/non-existing-file: open /tmp/non-existing-file: no such file or directory`,
},
}
for _, test := range tests {
if test.user == "" {
test.user = user
}
t.Run(test.desc, func(tt *testing.T) {
testSign(tt, test.ctx, test.signaturePaths, test.signatureNames, test.expectedErrMsg)
testSign(tt, test.ctx, test.signaturePaths, test.signatureNames, test.user, test.expectedErrMsg)
})
}
}
const user = "nopass"
func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signatureNames []string, expectedErrMsg string) {
func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signatureNames []string, user, expectedErrMsg string) {
// create temp dir for file and signature
tmpdir, err := ioutil.TempDir("", "goreleaser")
assert.NoError(t, err)
@ -411,7 +498,7 @@ func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signa
// verify the signatures
for _, sig := range signaturePaths {
verifySignature(t, ctx, sig)
verifySignature(t, ctx, sig, user)
}
var signArtifacts []string
@ -422,7 +509,7 @@ func testSign(t *testing.T, ctx *context.Context, signaturePaths []string, signa
assert.ElementsMatch(t, signArtifacts, signatureNames)
}
func verifySignature(t *testing.T, ctx *context.Context, sig string) {
func verifySignature(t *testing.T, ctx *context.Context, sig string, user string) {
artifact := strings.Replace(sig, filepath.Ext(sig), "", 1)
// verify signature was made with key for usesr 'nopass'

View File

@ -0,0 +1,32 @@
Ceci est un certificat de révocation pour la clef OpenPGP :
pub rsa2048 2020-08-24 [S]
FB57B0585968EADC1DA28A2D4340E38ACDF3A2EF
uid password
A revocation certificate is a kind of "kill switch" to publicly
declare that a key shall not anymore be used. It is not possible
to retract such a revocation certificate once it has been published.
Use it to revoke this key in case of a compromise or loss of
the secret key. However, if the secret key is still accessible,
it is better to generate a new revocation certificate and give
a reason for the revocation. For details see the description of
of the gpg command "--generate-revocation" in the GnuPG manual.
To avoid an accidental use of this file, a colon has been inserted
before the 5 dashes below. Remove this colon with a text editor
before importing and publishing this revocation certificate.
:-----BEGIN PGP PUBLIC KEY BLOCK-----
Comment: This is a revocation certificate
iQE2BCABCAAgFiEE+1ewWFlo6twdoootQ0Djis3zou8FAl9Ddw8CHQAACgkQQ0Dj
is3zou9JJAf8CizfKMs2FxUyLVrxl46zKUsvABFdan9FCY24kg+1sEmiGO7pSqJ9
sja6hYaOU1qE3LhqJ+ULqDaBX33ACYPRlkvhKrWV+EVS4Ppx22zu7Y/Vn+xskeJO
lh26eRXiHAJoCjMjnnLNa1gZUWSKVghZ9JhLhhZ/pyHQefsxGmc/nqrrx8SiiWPZ
wknfZ5f2DhABKOOkO7dZ72W3+ApwUF0T8z19kzn6ZaY3JM9GQOo/OIKuRFrmxbEu
2owjHd1NRO2xJaMqv+GlwyUZ55zBR248tHBqpvS46wNjftJqYHLgFqrMGYju7nJe
it1y6FjfT2zLgPqPLcpBdaynd8+rJQo1QQ==
=cnWP
-----END PGP PUBLIC KEY BLOCK-----

View File

@ -0,0 +1 @@
password

Binary file not shown.

Binary file not shown.

View File

@ -347,6 +347,8 @@ type Sign struct {
Signature string `yaml:"signature,omitempty"`
Artifacts string `yaml:"artifacts,omitempty"`
IDs []string `yaml:"ids,omitempty"`
Stdin *string `yaml:"stdin,omitempty"`
StdinFile string `yaml:"stdin_file,omitempty"`
}
// SnapcraftAppMetadata for the binaries that will be in the snap package.

View File

@ -69,6 +69,14 @@ signs:
ids:
- foo
- bar
# Stdin data to be given to the signature command as stdin.
# defaults to empty
stdin: password
# StdinFile file to be given to the signature command as stdin.
# defaults to empty
stdin_file: ./.password
```
### Limitations