Add gpg key path and passphrase as args

Signed-off-by: Somtochi Onyekwere <somtochionyekwere@gmail.com>
pull/1854/head
Somtochi Onyekwere 3 years ago
parent b9ceceada4
commit 0beab87f5b

@ -68,6 +68,10 @@ type bootstrapFlags struct {
authorName string
authorEmail string
gpgKeyPath string
gpgPassphrase string
gpgKeyID string
commitMessageAppendix string
}
@ -119,6 +123,10 @@ func init() {
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorName, "author-name", "Flux", "author name for Git commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.authorEmail, "author-email", "", "author email for Git commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyPath, "gpg-key", "", "path to secret gpg key for signing commits")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgPassphrase, "gpg-passphrase", "", "passphrase for decrypting secret gpg key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.gpgKeyID, "gpg-key-id", "", "key id for selecting a particular key")
bootstrapCmd.PersistentFlags().StringVar(&bootstrapArgs.commitMessageAppendix, "commit-message-appendix", "", "string to add to the commit messages, e.g. '[ci skip]'")
bootstrapCmd.PersistentFlags().Var(&bootstrapArgs.arch, "arch", bootstrapArgs.arch.Description())

@ -224,6 +224,7 @@ func bootstrapGitCmdRun(cmd *cobra.Command, args []string) error {
bootstrap.WithPostGenerateSecretFunc(promptPublicKey),
bootstrap.WithLogger(logger),
bootstrap.WithCABundle(caBundle),
bootstrap.WithGitCommitSigning(bootstrapArgs.gpgKeyPath, bootstrapArgs.gpgPassphrase, bootstrapArgs.gpgKeyID),
}
// Setup bootstrapper with constructed configs

@ -4,6 +4,7 @@ go 1.16
require (
github.com/Masterminds/semver/v3 v3.1.0
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
github.com/cyphar/filepath-securejoin v0.2.2
github.com/fluxcd/go-git-providers v0.1.1
github.com/fluxcd/helm-controller/api v0.11.2

@ -53,6 +53,10 @@ type PlainGitBootstrapper struct {
author git.Author
commitMessageAppendix string
gpgKeyPath string
gpgPassphrase string
gpgKeyID string
kubeconfig string
kubecontext string
@ -142,6 +146,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
}
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyPath, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version)
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
@ -149,7 +154,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
commit, err := b.git.Commit(git.Commit{
Author: b.author,
Message: commitMsg,
})
}, gpgOpts)
if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err)
}
@ -306,6 +311,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
b.logger.Successf("generated sync manifests")
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyPath, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux sync manifests")
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
@ -313,7 +319,8 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
commit, err := b.git.Commit(git.Commit{
Author: b.author,
Message: commitMsg,
})
}, gpgOpts)
if err != nil && err != git.ErrNoStagedFiles {
return fmt.Errorf("failed to commit sync manifests: %w", err)
}

@ -0,0 +1,37 @@
package git
// Option is a some configuration that modifies options for a commit.
type Option interface {
// ApplyToCommit applies this configuration to a given commit option.
ApplyToCommit(*CommitOptions)
}
// CommitOptions contains options for making a commit.
type CommitOptions struct {
*GPGSigningInfo
}
// GPGSigningInfo contains information for signing a commit.
type GPGSigningInfo struct {
PrivateKeyPath string
Passphrase string
KeyID string
}
type GpgSigningOption struct {
*GPGSigningInfo
}
func (w GpgSigningOption) ApplyToCommit(in *CommitOptions) {
in.GPGSigningInfo = w.GPGSigningInfo
}
func WithGpgSigningOption(path, passphrase, keyID string) Option {
return GpgSigningOption{
GPGSigningInfo: &GPGSigningInfo{
PrivateKeyPath: path,
Passphrase: passphrase,
KeyID: keyID,
},
}
}

@ -44,7 +44,7 @@ type Git interface {
Init(url, branch string) (bool, error)
Clone(ctx context.Context, url, branch string, caBundle []byte) (bool, error)
Write(path string, reader io.Reader) error
Commit(message Commit) (string, error)
Commit(message Commit, options ...Option) (string, error)
Push(ctx context.Context, caBundle []byte) error
Status() (bool, error)
Head() (string, error)

@ -25,6 +25,7 @@ import (
"strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
gogit "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
@ -40,6 +41,12 @@ type GoGit struct {
repository *gogit.Repository
}
type CommitOptions struct {
GpgKeyPath string
GpgKeyPassphrase string
KeyID string
}
func New(path string, auth transport.AuthMethod) *GoGit {
return &GoGit{
path: path,
@ -127,7 +134,7 @@ func (g *GoGit) Write(path string, reader io.Reader) error {
return err
}
func (g *GoGit) Commit(message git.Commit) (string, error) {
func (g *GoGit) Commit(message git.Commit, opts ...git.Option) (string, error) {
if g.repository == nil {
return "", git.ErrNoGitRepository
}
@ -142,6 +149,12 @@ func (g *GoGit) Commit(message git.Commit) (string, error) {
return "", err
}
// apply the options
options := &git.CommitOptions{}
for _, opt := range opts {
opt.ApplyToCommit(options)
}
// go-git has [a bug](https://github.com/go-git/go-git/issues/253)
// whereby it thinks broken symlinks to absolute paths are
// modified. There's no circumstance in which we want to commit a
@ -173,13 +186,24 @@ func (g *GoGit) Commit(message git.Commit) (string, error) {
return head.Hash().String(), git.ErrNoStagedFiles
}
commit, err := wt.Commit(message.Message, &gogit.CommitOptions{
commitOpts := &gogit.CommitOptions{
Author: &object.Signature{
Name: message.Name,
Email: message.Email,
When: time.Now(),
},
})
}
if options.GPGSigningInfo != nil {
entity, err := getOpenPgpEntity(*options.GPGSigningInfo)
if err != nil {
return "", err
}
commitOpts.SignKey = entity
}
commit, err := wt.Commit(message.Message, commitOpts)
if err != nil {
return "", err
}
@ -232,3 +256,41 @@ func (g *GoGit) Path() string {
func isRemoteBranchNotFoundErr(err error, ref string) bool {
return strings.Contains(err.Error(), fmt.Sprintf("couldn't find remote ref %q", ref))
}
func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
r, err := os.Open(info.PrivateKeyPath)
if err != nil {
return nil, err
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
if len(entityList) == 0 {
return nil, fmt.Errorf("no entity formed")
}
var entity *openpgp.Entity
if info.KeyID != "" {
for _, ent := range entityList {
if ent.PrimaryKey.KeyIdString() == info.KeyID {
entity = ent
}
}
if entity == nil {
return nil, fmt.Errorf("no key matching the key id was found")
}
} else {
entity = entityList[0]
}
err = entity.PrivateKey.Decrypt([]byte(info.Passphrase))
if err != nil {
return nil, err
}
return entity, nil
}

@ -0,0 +1,66 @@
// +build unit
package gogit
import (
"testing"
"github.com/fluxcd/flux2/internal/bootstrap/git"
)
func TestGetOpenPgpEntity(t *testing.T) {
tests := []struct {
name string
keyPath string
passphrase string
id string
expectErr bool
}{
{
name: "no default key id given",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "",
expectErr: false,
},
{
name: "key id given",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "0619327DBD777415",
expectErr: false,
},
{
name: "wrong key id",
keyPath: "testdata/private.key",
passphrase: "flux",
id: "0619327DBD777416",
expectErr: true,
},
{
name: "wrong password",
keyPath: "testdata/private.key",
passphrase: "fluxe",
id: "0619327DBD777415",
expectErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
gpgInfo := git.GPGSigningInfo{
PrivateKeyPath: tt.keyPath,
Passphrase: tt.passphrase,
KeyID: tt.id,
}
_, err := getOpenPgpEntity(gpgInfo)
if err != nil && !tt.expectErr {
t.Errorf("unexpected error: %s", err)
}
if err == nil && tt.expectErr {
t.Errorf("expected error when %s", tt.name)
}
})
}
}

@ -112,3 +112,27 @@ func (o loggerOption) applyGit(b *PlainGitBootstrapper) {
func (o loggerOption) applyGitProvider(b *GitProviderBootstrapper) {
b.logger = o.logger
}
func WithGitCommitSigning(path, passphrase, keyID string) Option {
return gitCommitSigningOption{
gpgKeyPath: path,
gpgPassphrase: passphrase,
gpgKeyID: keyID,
}
}
type gitCommitSigningOption struct {
gpgKeyPath string
gpgPassphrase string
gpgKeyID string
}
func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {
b.gpgPassphrase = o.gpgPassphrase
b.gpgKeyPath = o.gpgKeyPath
b.gpgKeyID = o.gpgKeyID
}
func (o gitCommitSigningOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}

Loading…
Cancel
Save