Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 25 additions & 88 deletions controllers/gitrepository_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,12 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"time"

"github.com/blang/semver"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand All @@ -40,6 +36,7 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"

sourcev1 "github.com/fluxcd/source-controller/api/v1alpha1"
internalgit "github.com/fluxcd/source-controller/internal/git"
)

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

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

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

// create tmp dir for SSH known_hosts
tmpSSH, err := ioutil.TempDir("", repository.Name)
if err != nil {
err = fmt.Errorf("tmp dir error: %w", err)
return sourcev1.GitRepositoryNotReady(repository, sourcev1.StorageOperationFailedReason, err.Error()), err
}
defer os.RemoveAll(tmpSSH)
var auth transport.AuthMethod
if repository.Spec.SecretRef != nil {
name := types.NamespacedName{
Namespace: repository.GetNamespace(),
Name: repository.Spec.SecretRef.Name,
}

auth, err := r.auth(repository, tmpSSH)
if err != nil {
err = fmt.Errorf("auth error: %w", err)
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
var secret corev1.Secret
err := r.Client.Get(ctx, name, &secret)
if err != nil {
err = fmt.Errorf("auth secret error: %w", err)
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
}

method, cleanup, err := internalgit.AuthMethodFromSecret(repository.Spec.URL, secret)
if err != nil {
err = fmt.Errorf("auth error: %w", err)
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
}
if cleanup != nil {
defer cleanup()
}
auth = method
}

// create tmp dir for the Git clone
Expand Down Expand Up @@ -321,74 +329,3 @@ func (r *GitRepositoryReconciler) gc(repository sourcev1.GitRepository) {
}
}
}

func (r *GitRepositoryReconciler) auth(repository sourcev1.GitRepository, tmp string) (transport.AuthMethod, error) {
if repository.Spec.SecretRef == nil {
return nil, nil
}

name := types.NamespacedName{
Namespace: repository.GetNamespace(),
Name: repository.Spec.SecretRef.Name,
}

var secret corev1.Secret
err := r.Client.Get(context.TODO(), name, &secret)
if err != nil {
return nil, err
}

credentials := secret.Data

// HTTP auth
if strings.HasPrefix(repository.Spec.URL, "http") {
auth := &http.BasicAuth{}
if username, ok := credentials["username"]; ok {
auth.Username = string(username)
}
if password, ok := credentials["password"]; ok {
auth.Password = string(password)
}

if auth.Username == "" || auth.Password == "" {
return nil, fmt.Errorf("invalid '%s' secret data: required fields username and password",
repository.Spec.SecretRef.Name)
}

return auth, nil
}

// SSH auth
if strings.HasPrefix(repository.Spec.URL, "ssh") {
var privateKey []byte
if identity, ok := credentials["identity"]; ok {
privateKey = identity
} else {
return nil, fmt.Errorf("invalid '%s' secret data: required field identity", repository.Spec.SecretRef.Name)
}

pk, err := ssh.NewPublicKeys("git", privateKey, "")
if err != nil {
return nil, err
}

known_hosts := filepath.Join(tmp, "known_hosts")
if kh, ok := credentials["known_hosts"]; ok {
if err := ioutil.WriteFile(filepath.Join(tmp, "known_hosts"), kh, 0644); err != nil {
return nil, err
}
} else {
return nil, fmt.Errorf("invalid '%s' secret data: required field known_hosts", repository.Spec.SecretRef.Name)
}

callback, err := ssh.NewKnownHostsCallback(known_hosts)
if err != nil {
return nil, err
}
pk.HostKeyCallback = callback

return pk, nil
}

return nil, nil
}
73 changes: 73 additions & 0 deletions internal/git/transport.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package git

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"

"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
corev1 "k8s.io/api/core/v1"
)

func AuthMethodFromSecret(url string, secret corev1.Secret) (transport.AuthMethod, func(), error) {
switch {
case strings.HasPrefix(url, "http"):
auth, err := BasicAuthFromSecret(secret)
return auth, nil, err
case strings.HasPrefix(url, "ssh"):
return PublicKeysFromSecret(secret)
}
return nil, nil, nil
}

func BasicAuthFromSecret(secret corev1.Secret) (*http.BasicAuth, error) {
auth := &http.BasicAuth{}
if username, ok := secret.Data["username"]; ok {
auth.Username = string(username)
}
if password, ok := secret.Data["password"]; ok {
auth.Password = string(password)
}
if auth.Username == "" || auth.Password == "" {
return nil, fmt.Errorf("invalid '%s' secret data: required fields 'username' and 'password'", secret.Name)
}
return auth, nil
}

func PublicKeysFromSecret(secret corev1.Secret) (*ssh.PublicKeys, func(), error) {
identity := secret.Data["identity"]
knownHosts := secret.Data["known_hosts"]
if len(identity) == 0 || len(knownHosts) == 0 {
return nil, nil, fmt.Errorf("invalid '%s' secret data: required fields 'identity' and 'known_hosts'", secret.Name)
}

pk, err := ssh.NewPublicKeys("git", identity, "")
if err != nil {
return nil, nil, err
}

// create tmp dir for known_hosts
tmp, err := ioutil.TempDir("", "ssh-"+secret.Name)
if err != nil {
return nil, nil, err
}
cleanup := func() { os.RemoveAll(tmp) }

knownHostsPath := filepath.Join(tmp, "known_hosts")
if err := ioutil.WriteFile(knownHostsPath, knownHosts, 0644); err != nil {
cleanup()
return nil, nil, err
}

callback, err := ssh.NewKnownHostsCallback(knownHostsPath)
if err != nil {
cleanup()
return nil, nil, err
}
pk.HostKeyCallback = callback
return pk, cleanup, nil
}