1
0
mirror of synced 2026-02-06 19:05:55 +00:00

Remove file reading from bootstrap package

Signed-off-by: Philip Laine <philip.laine@gmail.com>
This commit is contained in:
Philip Laine
2022-10-24 11:11:33 +02:00
parent 2c267c95e5
commit a4734d7e30
17 changed files with 270 additions and 168 deletions

View File

@@ -24,6 +24,7 @@ import (
"strings"
"time"
"github.com/ProtonMail/go-crypto/openpgp"
gogit "github.com/go-git/go-git/v5"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -56,9 +57,9 @@ type PlainGitBootstrapper struct {
author git.Author
commitMessageAppendix string
gpgKeyRingPath string
gpgPassphrase string
gpgKeyID string
gpgKeyRing openpgp.EntityList
gpgPassphrase string
gpgKeyID string
restClientGetter genericclioptions.RESTClientGetter
restClientOptions *runclient.Options
@@ -139,7 +140,7 @@ func (b *PlainGitBootstrapper) ReconcileComponents(ctx context.Context, manifest
}
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRingPath, b.gpgPassphrase, b.gpgKeyID)
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux %s component manifests", options.Version)
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix
@@ -195,7 +196,7 @@ func (b *PlainGitBootstrapper) ReconcileSourceSecret(ctx context.Context, option
}
// Return early if exists and no custom config is passed
if ok && len(options.CAFilePath+options.PrivateKeyPath+options.Username+options.Password) == 0 {
if ok && options.Keypair == nil && len(options.CAFile) == 0 && len(options.Username+options.Password) == 0 {
b.logger.Successf("source secret up to date")
return nil
}
@@ -284,7 +285,7 @@ func (b *PlainGitBootstrapper) ReconcileSyncConfig(ctx context.Context, options
b.logger.Successf("generated sync manifests")
// Git commit generated
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRingPath, b.gpgPassphrase, b.gpgKeyID)
gpgOpts := git.WithGpgSigningOption(b.gpgKeyRing, b.gpgPassphrase, b.gpgKeyID)
commitMsg := fmt.Sprintf("Add Flux sync manifests")
if b.commitMessageAppendix != "" {
commitMsg = commitMsg + "\n\n" + b.commitMessageAppendix

View File

@@ -1,5 +1,9 @@
package git
import (
"github.com/ProtonMail/go-crypto/openpgp"
)
// Option is a some configuration that modifies options for a commit.
type Option interface {
// ApplyToCommit applies this configuration to a given commit option.
@@ -13,9 +17,9 @@ type CommitOptions struct {
// GPGSigningInfo contains information for signing a commit.
type GPGSigningInfo struct {
KeyRingPath string
Passphrase string
KeyID string
KeyRing openpgp.EntityList
Passphrase string
KeyID string
}
type GpgSigningOption struct {
@@ -26,17 +30,17 @@ func (w GpgSigningOption) ApplyToCommit(in *CommitOptions) {
in.GPGSigningInfo = w.GPGSigningInfo
}
func WithGpgSigningOption(path, passphrase, keyID string) Option {
func WithGpgSigningOption(keyRing openpgp.EntityList, passphrase, keyID string) Option {
// Return nil if no path is set, even if other options are configured.
if path == "" {
if len(keyRing) == 0 {
return GpgSigningOption{}
}
return GpgSigningOption{
GPGSigningInfo: &GPGSigningInfo{
KeyRingPath: path,
Passphrase: passphrase,
KeyID: keyID,
KeyRing: keyRing,
Passphrase: passphrase,
KeyID: keyID,
},
}
}

View File

@@ -258,23 +258,13 @@ func isRemoteBranchNotFoundErr(err error, ref string) bool {
}
func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
r, err := os.Open(info.KeyRingPath)
if err != nil {
return nil, fmt.Errorf("unable to open GPG key ring: %w", err)
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
if len(entityList) == 0 {
if len(info.KeyRing) == 0 {
return nil, fmt.Errorf("empty GPG key ring")
}
var entity *openpgp.Entity
if info.KeyID != "" {
for _, ent := range entityList {
for _, ent := range info.KeyRing {
if ent.PrimaryKey.KeyIdString() == info.KeyID {
entity = ent
}
@@ -284,10 +274,10 @@ func getOpenPgpEntity(info git.GPGSigningInfo) (*openpgp.Entity, error) {
return nil, fmt.Errorf("no GPG private key matching key id '%s' found", info.KeyID)
}
} else {
entity = entityList[0]
entity = info.KeyRing[0]
}
err = entity.PrivateKey.Decrypt([]byte(info.Passphrase))
err := entity.PrivateKey.Decrypt([]byte(info.Passphrase))
if err != nil {
return nil, fmt.Errorf("unable to decrypt GPG private key: %w", err)
}

View File

@@ -4,8 +4,10 @@
package gogit
import (
"os"
"testing"
"github.com/ProtonMail/go-crypto/openpgp"
"github.com/fluxcd/flux2/pkg/bootstrap/git"
)
@@ -49,10 +51,21 @@ func TestGetOpenPgpEntity(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var entityList openpgp.EntityList
if tt.keyPath != "" {
r, err := os.Open(tt.keyPath)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
entityList, err = openpgp.ReadKeyRing(r)
if err != nil {
t.Errorf("unexpected error: %s", err)
}
}
gpgInfo := git.GPGSigningInfo{
KeyRingPath: tt.keyPath,
Passphrase: tt.passphrase,
KeyID: tt.id,
KeyRing: entityList,
Passphrase: tt.passphrase,
KeyID: tt.id,
}
_, err := getOpenPgpEntity(gpgInfo)

View File

@@ -17,8 +17,12 @@ limitations under the License.
package bootstrap
import (
"fmt"
"os"
"k8s.io/cli-runtime/pkg/genericclioptions"
"github.com/ProtonMail/go-crypto/openpgp"
runclient "github.com/fluxcd/pkg/runtime/client"
"github.com/fluxcd/flux2/pkg/bootstrap/git"
@@ -131,22 +135,22 @@ func (o loggerOption) applyGitProvider(b *GitProviderBootstrapper) {
b.logger = o.logger
}
func WithGitCommitSigning(path, passphrase, keyID string) Option {
func WithGitCommitSigning(gpgKeyRing openpgp.EntityList, passphrase, keyID string) Option {
return gitCommitSigningOption{
gpgKeyRingPath: path,
gpgPassphrase: passphrase,
gpgKeyID: keyID,
gpgKeyRing: gpgKeyRing,
gpgPassphrase: passphrase,
gpgKeyID: keyID,
}
}
type gitCommitSigningOption struct {
gpgKeyRingPath string
gpgPassphrase string
gpgKeyID string
gpgKeyRing openpgp.EntityList
gpgPassphrase string
gpgKeyID string
}
func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {
b.gpgKeyRingPath = o.gpgKeyRingPath
b.gpgKeyRing = o.gpgKeyRing
b.gpgPassphrase = o.gpgPassphrase
b.gpgKeyID = o.gpgKeyID
}
@@ -154,3 +158,18 @@ func (o gitCommitSigningOption) applyGit(b *PlainGitBootstrapper) {
func (o gitCommitSigningOption) applyGitProvider(b *GitProviderBootstrapper) {
o.applyGit(b.PlainGitBootstrapper)
}
func LoadEntityListFromPath(path string) (openpgp.EntityList, error) {
if path == "" {
return nil, nil
}
r, err := os.Open(path)
if err != nil {
return nil, fmt.Errorf("unable to open GPG key ring: %w", err)
}
entityList, err := openpgp.ReadKeyRing(r)
if err != nil {
return nil, err
}
return entityList, nil
}

View File

@@ -18,6 +18,8 @@ package sourcesecret
import (
"crypto/elliptic"
"github.com/fluxcd/pkg/ssh"
)
type PrivateKeyAlgorithm string
@@ -48,12 +50,12 @@ type Options struct {
PrivateKeyAlgorithm PrivateKeyAlgorithm
RSAKeyBits int
ECDSACurve elliptic.Curve
PrivateKeyPath string
Keypair *ssh.KeyPair
Username string
Password string
CAFilePath string
CertFilePath string
KeyFilePath string
CAFile []byte
CertFile []byte
KeyFile []byte
TargetPath string
ManifestFile string
}
@@ -64,12 +66,11 @@ func MakeDefaultOptions() Options {
Namespace: "flux-system",
Labels: map[string]string{},
PrivateKeyAlgorithm: RSAPrivateKeyAlgorithm,
PrivateKeyPath: "",
Username: "",
Password: "",
CAFilePath: "",
CertFilePath: "",
KeyFilePath: "",
CAFile: []byte{},
CertFile: []byte{},
KeyFile: []byte{},
ManifestFile: "secret.yaml",
}
}

View File

@@ -66,10 +66,8 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
switch {
case options.Username != "" && options.Password != "":
// noop
case len(options.PrivateKeyPath) > 0:
if keypair, err = loadKeyPair(options.PrivateKeyPath, options.Password); err != nil {
return nil, err
}
case options.Keypair != nil:
keypair = options.Keypair
case len(options.PrivateKeyAlgorithm) > 0:
if keypair, err = generateKeyPair(options); err != nil {
return nil, err
@@ -83,23 +81,6 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
}
}
var caFile []byte
if options.CAFilePath != "" {
if caFile, err = os.ReadFile(options.CAFilePath); err != nil {
return nil, fmt.Errorf("failed to read CA file: %w", err)
}
}
var certFile, keyFile []byte
if options.CertFilePath != "" && options.KeyFilePath != "" {
if certFile, err = os.ReadFile(options.CertFilePath); err != nil {
return nil, fmt.Errorf("failed to read cert file: %w", err)
}
if keyFile, err = os.ReadFile(options.KeyFilePath); err != nil {
return nil, fmt.Errorf("failed to read key file: %w", err)
}
}
var dockerCfgJson []byte
if options.Registry != "" {
dockerCfgJson, err = generateDockerConfigJson(options.Registry, options.Username, options.Password)
@@ -108,7 +89,7 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
}
}
secret := buildSecret(keypair, hostKey, caFile, certFile, keyFile, dockerCfgJson, options)
secret := buildSecret(keypair, hostKey, options.CAFile, options.CertFile, options.KeyFile, dockerCfgJson, options)
b, err := yaml.Marshal(secret)
if err != nil {
return nil, err
@@ -120,6 +101,35 @@ func Generate(options Options) (*manifestgen.Manifest, error) {
}, nil
}
func LoadKeyPairFromPath(path, password string) (*ssh.KeyPair, error) {
if path == "" {
return nil, nil
}
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to open private key file: %w", err)
}
return LoadKeyPair(b, password)
}
func LoadKeyPair(privateKey []byte, password string) (*ssh.KeyPair, error) {
var ppk cryptssh.Signer
var err error
if password != "" {
ppk, err = cryptssh.ParsePrivateKeyWithPassphrase(privateKey, []byte(password))
} else {
ppk, err = cryptssh.ParsePrivateKey(privateKey)
}
if err != nil {
return nil, err
}
return &ssh.KeyPair{
PublicKey: cryptssh.MarshalAuthorizedKey(ppk.PublicKey()),
PrivateKey: privateKey,
}, nil
}
func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, dockerCfg []byte, options Options) (secret corev1.Secret) {
secret.TypeMeta = metav1.TypeMeta{
APIVersion: "v1",
@@ -143,16 +153,16 @@ func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, docke
secret.StringData[PasswordSecretKey] = options.Password
}
if caFile != nil {
if len(caFile) != 0 {
secret.StringData[CAFileSecretKey] = string(caFile)
}
if certFile != nil && keyFile != nil {
if len(certFile) != 0 && len(keyFile) != 0 {
secret.StringData[CertFileSecretKey] = string(certFile)
secret.StringData[KeyFileSecretKey] = string(keyFile)
}
if keypair != nil && hostKey != nil {
if keypair != nil && len(hostKey) != 0 {
secret.StringData[PrivateKeySecretKey] = string(keypair.PrivateKey)
secret.StringData[PublicKeySecretKey] = string(keypair.PublicKey)
secret.StringData[KnownHostsSecretKey] = string(hostKey)
@@ -165,29 +175,6 @@ func buildSecret(keypair *ssh.KeyPair, hostKey, caFile, certFile, keyFile, docke
return
}
func loadKeyPair(path string, password string) (*ssh.KeyPair, error) {
b, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("failed to open private key file: %w", err)
}
var ppk cryptssh.Signer
if password != "" {
ppk, err = cryptssh.ParsePrivateKeyWithPassphrase(b, []byte(password))
} else {
ppk, err = cryptssh.ParsePrivateKey(b)
}
if err != nil {
return nil, err
}
return &ssh.KeyPair{
PublicKey: cryptssh.MarshalAuthorizedKey(ppk.PublicKey()),
PrivateKey: b,
}, nil
}
func generateKeyPair(options Options) (*ssh.KeyPair, error) {
var keyGen ssh.KeyPairGenerator
switch options.PrivateKeyAlgorithm {

View File

@@ -48,7 +48,7 @@ func Test_passwordLoadKeyPair(t *testing.T) {
pk, _ := os.ReadFile(tt.privateKeyPath)
ppk, _ := os.ReadFile(tt.publicKeyPath)
got, err := loadKeyPair(tt.privateKeyPath, tt.password)
got, err := LoadKeyPair(pk, tt.password)
if err != nil {
t.Errorf("loadKeyPair() error = %v", err)
return
@@ -67,24 +67,13 @@ func Test_passwordLoadKeyPair(t *testing.T) {
func Test_PasswordlessLoadKeyPair(t *testing.T) {
for algo, privateKey := range testdata.PEMBytes {
t.Run(algo, func(t *testing.T) {
f, err := os.CreateTemp("", "test-private-key-")
if err != nil {
t.Fatalf("unable to create temporary file. err: %s", err)
}
defer os.Remove(f.Name())
if _, err = f.Write(privateKey); err != nil {
t.Fatalf("unable to write private key to file. err: %s", err)
}
got, err := loadKeyPair(f.Name(), "")
got, err := LoadKeyPair(privateKey, "")
if err != nil {
t.Errorf("loadKeyPair() error = %v", err)
return
}
pk, _ := os.ReadFile(f.Name())
if !reflect.DeepEqual(got.PrivateKey, pk) {
if !reflect.DeepEqual(got.PrivateKey, privateKey) {
t.Errorf("PrivateKey %s != %s", got.PrivateKey, string(privateKey))
}