Skip to content

Commit 90348eb

Browse files
committed
Internal helpers for Git auth methods from secrets
1 parent b7897b7 commit 90348eb

File tree

2 files changed

+98
-88
lines changed

2 files changed

+98
-88
lines changed

controllers/gitrepository_controller.go

Lines changed: 25 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,12 @@ import (
2121
"fmt"
2222
"io/ioutil"
2323
"os"
24-
"path/filepath"
25-
"strings"
2624
"time"
2725

2826
"github.com/blang/semver"
2927
"github.com/go-git/go-git/v5"
3028
"github.com/go-git/go-git/v5/plumbing"
3129
"github.com/go-git/go-git/v5/plumbing/transport"
32-
"github.com/go-git/go-git/v5/plumbing/transport/http"
33-
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
3430
"github.com/go-logr/logr"
3531
corev1 "k8s.io/api/core/v1"
3632
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -40,6 +36,7 @@ import (
4036
"sigs.k8s.io/controller-runtime/pkg/client"
4137

4238
sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
39+
internalgit "github.com/fluxcd/source-controller/internal/git"
4340
)
4441

4542
// GitRepositoryReconciler reconciles a GitRepository object
@@ -78,7 +75,7 @@ func (r *GitRepositoryReconciler) Reconcile(req ctrl.Request) (ctrl.Result, erro
7875
r.gc(repo)
7976

8077
// try git clone
81-
syncedRepo, err := r.sync(*repo.DeepCopy())
78+
syncedRepo, err := r.sync(ctx, *repo.DeepCopy())
8279
if err != nil {
8380
log.Info("Git repository sync failed", "error", err.Error())
8481
}
@@ -103,7 +100,7 @@ func (r *GitRepositoryReconciler) SetupWithManager(mgr ctrl.Manager) error {
103100
Complete(r)
104101
}
105102

106-
func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourcev1.GitRepository, error) {
103+
func (r *GitRepositoryReconciler) sync(ctx context.Context, repository sourcev1.GitRepository) (sourcev1.GitRepository, error) {
107104
// set defaults: master branch, no tags fetching, max two commits
108105
branch := "master"
109106
revision := ""
@@ -129,18 +126,29 @@ func (r *GitRepositoryReconciler) sync(repository sourcev1.GitRepository) (sourc
129126
}
130127
}
131128

132-
// create tmp dir for SSH known_hosts
133-
tmpSSH, err := ioutil.TempDir("", repository.Name)
134-
if err != nil {
135-
err = fmt.Errorf("tmp dir error: %w", err)
136-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
137-
}
138-
defer os.RemoveAll(tmpSSH)
129+
var auth transport.AuthMethod
130+
if repository.Spec.SecretRef != nil {
131+
name := types.NamespacedName{
132+
Namespace: repository.GetNamespace(),
133+
Name: repository.Spec.SecretRef.Name,
134+
}
139135

140-
auth, err := r.auth(repository, tmpSSH)
141-
if err != nil {
142-
err = fmt.Errorf("auth error: %w", err)
143-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
136+
var secret corev1.Secret
137+
err := r.Client.Get(ctx, name, &secret)
138+
if err != nil {
139+
err = fmt.Errorf("auth secret error: %w", err)
140+
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
141+
}
142+
143+
method, cleanup, err := internalgit.AuthMethodFromSecret(repository.Spec.URL, secret)
144+
if err != nil {
145+
err = fmt.Errorf("auth error: %w", err)
146+
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
147+
}
148+
if cleanup != nil {
149+
defer cleanup()
150+
}
151+
auth = method
144152
}
145153

146154
// create tmp dir for the Git clone
@@ -321,74 +329,3 @@ func (r *GitRepositoryReconciler) gc(repository sourcev1.GitRepository) {
321329
}
322330
}
323331
}
324-
325-
func (r *GitRepositoryReconciler) auth(repository sourcev1.GitRepository, tmp string) (transport.AuthMethod, error) {
326-
if repository.Spec.SecretRef == nil {
327-
return nil, nil
328-
}
329-
330-
name := types.NamespacedName{
331-
Namespace: repository.GetNamespace(),
332-
Name: repository.Spec.SecretRef.Name,
333-
}
334-
335-
var secret corev1.Secret
336-
err := r.Client.Get(context.TODO(), name, &secret)
337-
if err != nil {
338-
return nil, err
339-
}
340-
341-
credentials := secret.Data
342-
343-
// HTTP auth
344-
if strings.HasPrefix(repository.Spec.URL, "http") {
345-
auth := &http.BasicAuth{}
346-
if username, ok := credentials["username"]; ok {
347-
auth.Username = string(username)
348-
}
349-
if password, ok := credentials["password"]; ok {
350-
auth.Password = string(password)
351-
}
352-
353-
if auth.Username == "" || auth.Password == "" {
354-
return nil, fmt.Errorf("invalid '%s' secret data: required fields username and password",
355-
repository.Spec.SecretRef.Name)
356-
}
357-
358-
return auth, nil
359-
}
360-
361-
// SSH auth
362-
if strings.HasPrefix(repository.Spec.URL, "ssh") {
363-
var privateKey []byte
364-
if identity, ok := credentials["identity"]; ok {
365-
privateKey = identity
366-
} else {
367-
return nil, fmt.Errorf("invalid '%s' secret data: required field identity", repository.Spec.SecretRef.Name)
368-
}
369-
370-
pk, err := ssh.NewPublicKeys("git", privateKey, "")
371-
if err != nil {
372-
return nil, err
373-
}
374-
375-
known_hosts := filepath.Join(tmp, "known_hosts")
376-
if kh, ok := credentials["known_hosts"]; ok {
377-
if err := ioutil.WriteFile(filepath.Join(tmp, "known_hosts"), kh, 0644); err != nil {
378-
return nil, err
379-
}
380-
} else {
381-
return nil, fmt.Errorf("invalid '%s' secret data: required field known_hosts", repository.Spec.SecretRef.Name)
382-
}
383-
384-
callback, err := ssh.NewKnownHostsCallback(known_hosts)
385-
if err != nil {
386-
return nil, err
387-
}
388-
pk.HostKeyCallback = callback
389-
390-
return pk, nil
391-
}
392-
393-
return nil, nil
394-
}

internal/git/transport.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package git
2+
3+
import (
4+
"fmt"
5+
"io/ioutil"
6+
"os"
7+
"path/filepath"
8+
"strings"
9+
10+
"github.com/go-git/go-git/v5/plumbing/transport"
11+
"github.com/go-git/go-git/v5/plumbing/transport/http"
12+
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
13+
corev1 "k8s.io/api/core/v1"
14+
)
15+
16+
func AuthMethodFromSecret(url string, secret corev1.Secret) (transport.AuthMethod, func(), error) {
17+
switch {
18+
case strings.HasPrefix(url, "http"):
19+
auth, err := BasicAuthFromSecret(secret)
20+
return auth, nil, err
21+
case strings.HasPrefix(url, "ssh"):
22+
return PublicKeysFromSecret(secret)
23+
}
24+
return nil, nil, nil
25+
}
26+
27+
func BasicAuthFromSecret(secret corev1.Secret) (*http.BasicAuth, error) {
28+
auth := &http.BasicAuth{}
29+
if username, ok := secret.Data["username"]; ok {
30+
auth.Username = string(username)
31+
}
32+
if password, ok := secret.Data["password"]; ok {
33+
auth.Password = string(password)
34+
}
35+
if auth.Username == "" || auth.Password == "" {
36+
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'username' and 'password'", secret.Name)
37+
}
38+
return auth, nil
39+
}
40+
41+
func PublicKeysFromSecret(secret corev1.Secret) (*ssh.PublicKeys, func(), error) {
42+
identity := secret.Data["identity"]
43+
knownHosts := secret.Data["known_hosts"]
44+
if len(identity) == 0 || len(knownHosts) == 0 {
45+
return nil, nil, fmt.Errorf("invalid '%s' secret data: required fields 'identity' and 'known_hosts'", secret.Name)
46+
}
47+
48+
pk, err := ssh.NewPublicKeys("git", identity, "")
49+
if err != nil {
50+
return nil, nil, err
51+
}
52+
53+
// create tmp dir for known_hosts
54+
tmp, err := ioutil.TempDir("", "ssh-"+secret.Name)
55+
if err != nil {
56+
return nil, nil, err
57+
}
58+
cleanup := func() { os.RemoveAll(tmp) }
59+
60+
knownHostsPath := filepath.Join(tmp, "known_hosts")
61+
if err := ioutil.WriteFile(knownHostsPath, knownHosts, 0644); err != nil {
62+
cleanup()
63+
return nil, nil, err
64+
}
65+
66+
callback, err := ssh.NewKnownHostsCallback(knownHostsPath)
67+
if err != nil {
68+
cleanup()
69+
return nil, nil, err
70+
}
71+
pk.HostKeyCallback = callback
72+
return pk, cleanup, nil
73+
}

0 commit comments

Comments
 (0)