Repository navigation
Expand file tree
/
Copy pathsigning.go
More file actions
128 lines (106 loc) · 3.82 KB
/
Copy pathsigning.go
File metadata and controls
128 lines (106 loc) · 3.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// SPDX-License-Identifier: Apache-2.0
package git
import (
"bytes"
"context"
"crypto/ed25519"
"crypto/rand"
"encoding/pem"
"errors"
"fmt"
"io"
"strings"
gogit "github.com/go-git/go-git/v6"
"golang.org/x/crypto/ssh"
corev1 "k8s.io/api/core/v1"
"github.com/ConfigButler/gitops-reverser/internal/sshsig"
)
const (
// SigningKeyDataKey is the Secret data key for the PEM-encoded SSH private signing key.
SigningKeyDataKey = "signing.key"
// SigningPublicKeyDataKey is the Secret data key for the authorized_keys-format public key.
SigningPublicKeyDataKey = "signing.pub"
// SigningPassphraseDataKey is the Secret data key for an optional key passphrase.
SigningPassphraseDataKey = "passphrase"
// Unexported aliases for internal use within this package.
signingKeyDataKey = SigningKeyDataKey
signingPublicKeyDataKey = SigningPublicKeyDataKey
signingPassphraseDataKey = SigningPassphraseDataKey
sshSignatureNamespace = "git"
)
type sshCommitSigner struct {
signer ssh.Signer
}
// LoadSSHCommitSigner loads a git-compatible SSH signer from the provided Secret.
func LoadSSHCommitSigner(secret *corev1.Secret) (gogit.Signer, error) {
signer, err := loadSSHSigner(secret)
if err != nil {
return nil, err
}
return &sshCommitSigner{signer: signer}, nil
}
// SSHAuthorizedPublicKeyFromSecret derives the authorized_keys-form public key from a signing Secret.
func SSHAuthorizedPublicKeyFromSecret(secret *corev1.Secret) (string, error) {
signer, err := loadSSHSigner(secret)
if err != nil {
return "", err
}
return strings.TrimSpace(string(ssh.MarshalAuthorizedKey(signer.PublicKey()))), nil
}
// GenerateSSHSigningKeyPair creates an ed25519 SSH signing keypair.
func GenerateSSHSigningKeyPair(passphrase []byte) ([]byte, []byte, error) {
_, privateKey, err := ed25519.GenerateKey(rand.Reader)
if err != nil {
return nil, nil, fmt.Errorf("generate ed25519 signing key: %w", err)
}
var block *pem.Block
if len(passphrase) > 0 {
block, err = ssh.MarshalPrivateKeyWithPassphrase(privateKey, "", passphrase)
} else {
block, err = ssh.MarshalPrivateKey(privateKey, "")
}
if err != nil {
return nil, nil, fmt.Errorf("marshal signing private key: %w", err)
}
privateKeyPEM := pem.EncodeToMemory(block)
publicKey, err := ssh.NewPublicKey(privateKey.Public())
if err != nil {
return nil, nil, fmt.Errorf("derive signing public key: %w", err)
}
return privateKeyPEM, bytes.TrimSpace(ssh.MarshalAuthorizedKey(publicKey)), nil
}
func loadSSHSigner(secret *corev1.Secret) (ssh.Signer, error) {
if secret == nil {
return nil, errors.New("signing secret is nil")
}
privateKey, ok := secret.Data[signingKeyDataKey]
if !ok || len(privateKey) == 0 {
return nil, fmt.Errorf("signing secret %s/%s is missing %q", secret.Namespace, secret.Name, signingKeyDataKey)
}
if passphrase := secret.Data[signingPassphraseDataKey]; len(passphrase) > 0 {
signer, err := sshsig.ParsePrivateKey(privateKey, passphrase)
if err != nil {
return nil, fmt.Errorf("parse passphrase-protected signing key from secret %s/%s: %w",
secret.Namespace, secret.Name, err)
}
return signer, nil
}
signer, err := sshsig.ParsePrivateKey(privateKey, nil)
if err != nil {
return nil, fmt.Errorf("parse signing key from secret %s/%s: %w", secret.Namespace, secret.Name, err)
}
return signer, nil
}
// Sign implements go-git v6's Signer, which passes a context so a signer can reach an external
// agent or KMS. Our signing is in-process, so the context is unused.
func (s *sshCommitSigner) Sign(_ context.Context, message io.Reader) ([]byte, error) {
payload, err := io.ReadAll(message)
if err != nil {
return nil, fmt.Errorf("read commit payload: %w", err)
}
signature, err := sshsig.SignMessage(s.signer, sshSignatureNamespace, payload)
if err != nil {
return nil, fmt.Errorf("sign commit payload: %w", err)
}
return signature, nil
}