Skip to content

Commit 5ccb914

Browse files
authored
Merge pull request #462 from fluxcd/git-auth-opts
2 parents 46a5b9c + d0ca107 commit 5ccb914

37 files changed

+2441
-835
lines changed

api/v1beta1/gitrepository_types.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ type GitRepositoryInclude struct {
120120
// GitRepositoryRef defines the Git ref used for pull and checkout operations.
121121
type GitRepositoryRef struct {
122122
// The Git branch to checkout, defaults to master.
123-
// +kubebuilder:default:=master
124123
// +optional
125124
Branch string `json:"branch,omitempty"`
126125

config/crd/bases/source.toolkit.fluxcd.io_gitrepositories.yaml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ spec:
9191
description: The Git reference to checkout and monitor for changes, defaults to master branch.
9292
properties:
9393
branch:
94-
default: master
9594
description: The Git branch to checkout, defaults to master.
9695
type: string
9796
commit:

controllers/gitrepository_controller.go

Lines changed: 23 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -229,58 +229,47 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
229229
}
230230
defer os.RemoveAll(tmpGit)
231231

232-
// determine auth method
233-
auth := &git.Auth{}
232+
// Configure auth options using secret
233+
var authOpts *git.AuthOptions
234234
if repository.Spec.SecretRef != nil {
235-
authStrategy, err := strategy.AuthSecretStrategyForURL(
236-
repository.Spec.URL,
237-
git.CheckoutOptions{
238-
GitImplementation: repository.Spec.GitImplementation,
239-
RecurseSubmodules: repository.Spec.RecurseSubmodules,
240-
})
241-
if err != nil {
242-
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
243-
}
244-
245235
name := types.NamespacedName{
246236
Namespace: repository.GetNamespace(),
247237
Name: repository.Spec.SecretRef.Name,
248238
}
249239

250-
var secret corev1.Secret
251-
err = r.Client.Get(ctx, name, &secret)
240+
secret := &corev1.Secret{}
241+
err = r.Client.Get(ctx, name, secret)
252242
if err != nil {
253243
err = fmt.Errorf("auth secret error: %w", err)
254244
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
255245
}
256246

257-
auth, err = authStrategy.Method(secret)
247+
authOpts, err = git.AuthOptionsFromSecret(repository.Spec.URL, secret)
258248
if err != nil {
259-
err = fmt.Errorf("auth error: %w", err)
260249
return sourcev1.GitRepositoryNotReady(repository, sourcev1.AuthenticationFailedReason, err.Error()), err
261250
}
262251
}
263-
264-
checkoutStrategy, err := strategy.CheckoutStrategyForRef(
265-
repository.Spec.Reference,
266-
git.CheckoutOptions{
267-
GitImplementation: repository.Spec.GitImplementation,
268-
RecurseSubmodules: repository.Spec.RecurseSubmodules,
269-
},
270-
)
252+
checkoutOpts := git.CheckoutOptions{RecurseSubmodules: repository.Spec.RecurseSubmodules}
253+
if ref := repository.Spec.Reference; ref != nil {
254+
checkoutOpts.Branch = ref.Branch
255+
checkoutOpts.Commit = ref.Commit
256+
checkoutOpts.Tag = ref.Tag
257+
checkoutOpts.SemVer = ref.SemVer
258+
}
259+
checkoutStrategy, err := strategy.CheckoutStrategyForImplementation(ctx,
260+
git.Implementation(repository.Spec.GitImplementation), checkoutOpts)
271261
if err != nil {
272262
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
273263
}
274264

275265
gitCtx, cancel := context.WithTimeout(ctx, repository.Spec.Timeout.Duration)
276266
defer cancel()
277267

278-
commit, revision, err := checkoutStrategy.Checkout(gitCtx, tmpGit, repository.Spec.URL, auth)
268+
commit, err := checkoutStrategy.Checkout(gitCtx, tmpGit, repository.Spec.URL, authOpts)
279269
if err != nil {
280270
return sourcev1.GitRepositoryNotReady(repository, sourcev1.GitOperationFailedReason, err.Error()), err
281271
}
282-
283-
artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), revision, fmt.Sprintf("%s.tar.gz", commit.Hash()))
272+
artifact := r.Storage.NewArtifactFor(repository.Kind, repository.GetObjectMeta(), commit.String(), fmt.Sprintf("%s.tar.gz", commit.Hash.String()))
284273

285274
// copy all included repository into the artifact
286275
includedArtifacts := []*sourcev1.Artifact{}
@@ -309,14 +298,17 @@ func (r *GitRepositoryReconciler) reconcile(ctx context.Context, repository sour
309298
Namespace: repository.Namespace,
310299
Name: repository.Spec.Verification.SecretRef.Name,
311300
}
312-
var secret corev1.Secret
313-
if err := r.Client.Get(ctx, publicKeySecret, &secret); err != nil {
301+
var secret *corev1.Secret
302+
if err := r.Client.Get(ctx, publicKeySecret, secret); err != nil {
314303
err = fmt.Errorf("PGP public keys secret error: %w", err)
315304
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
316305
}
317306

318-
err := commit.Verify(secret)
319-
if err != nil {
307+
var keyRings []string
308+
for _, v := range secret.Data {
309+
keyRings = append(keyRings, string(v))
310+
}
311+
if _, err = commit.Verify(keyRings...); err != nil {
320312
return sourcev1.GitRepositoryNotReady(repository, sourcev1.VerificationFailedReason, err.Error()), err
321313
}
322314
}

controllers/gitrepository_controller_test.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ import (
2323
"net/http"
2424
"net/url"
2525
"os"
26-
2726
"os/exec"
2827
"path"
2928
"path/filepath"
30-
3129
"strings"
3230
"time"
3331

@@ -251,7 +249,7 @@ var _ = Describe("GitRepositoryReconciler", func() {
251249
reference: &sourcev1.GitRepositoryRef{SemVer: "1.2.3.4"},
252250
waitForReason: sourcev1.GitOperationFailedReason,
253251
expectStatus: metav1.ConditionFalse,
254-
expectMessage: "semver parse range error: improper constraint: 1.2.3.4",
252+
expectMessage: "semver parse error: improper constraint: 1.2.3.4",
255253
}),
256254
Entry("semver no match", refTestCase{
257255
reference: &sourcev1.GitRepositoryRef{SemVer: "1.0.0"},
@@ -265,7 +263,7 @@ var _ = Describe("GitRepositoryReconciler", func() {
265263
},
266264
waitForReason: sourcev1.GitOperationSucceedReason,
267265
expectStatus: metav1.ConditionTrue,
268-
expectRevision: "master",
266+
expectRevision: "HEAD",
269267
}),
270268
Entry("commit in branch", refTestCase{
271269
reference: &sourcev1.GitRepositoryRef{
@@ -284,7 +282,7 @@ var _ = Describe("GitRepositoryReconciler", func() {
284282
},
285283
waitForReason: sourcev1.GitOperationFailedReason,
286284
expectStatus: metav1.ConditionFalse,
287-
expectMessage: "git commit 'invalid' not found: object not found",
285+
expectMessage: "failed to resolve commit object for 'invalid': object not found",
288286
}),
289287
)
290288

@@ -385,7 +383,7 @@ var _ = Describe("GitRepositoryReconciler", func() {
385383
reference: &sourcev1.GitRepositoryRef{Branch: "main"},
386384
waitForReason: sourcev1.GitOperationFailedReason,
387385
expectStatus: metav1.ConditionFalse,
388-
expectMessage: "error: user rejected certificate",
386+
expectMessage: "unable to clone: user rejected certificate",
389387
gitImplementation: sourcev1.LibGit2Implementation,
390388
}),
391389
Entry("self signed libgit2 with CA", refTestCase{

controllers/helmchart_controller.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,7 +529,7 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
529529

530530
v, err := semver.NewVersion(helmChart.Metadata.Version)
531531
if err != nil {
532-
err = fmt.Errorf("semver error: %w", err)
532+
err = fmt.Errorf("semver parse error: %w", err)
533533
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
534534
}
535535

@@ -539,7 +539,7 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
539539
splitRev := strings.Split(artifact.Revision, "/")
540540
v, err := v.SetMetadata(splitRev[len(splitRev)-1])
541541
if err != nil {
542-
err = fmt.Errorf("semver error: %w", err)
542+
err = fmt.Errorf("semver parse error: %w", err)
543543
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
544544
}
545545

docs/spec/v1beta1/gitrepositories.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,21 @@ spec:
273273
commit: 363a6a8fe6a7f13e05d34c163b0ef02a777da20a
274274
```
275275

276+
Checkout a specific commit:
277+
278+
```yaml
279+
apiVersion: source.toolkit.fluxcd.io/v1beta1
280+
kind: GitRepository
281+
metadata:
282+
name: podinfo
283+
namespace: default
284+
spec:
285+
interval: 1m
286+
url: https://github.com/stefanprodan/podinfo
287+
ref:
288+
commit: 363a6a8fe6a7f13e05d34c163b0ef02a777da20a
289+
```
290+
276291
Pull a specific tag:
277292

278293
```yaml

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,10 @@ require (
88
cloud.google.com/go v0.93.3 // indirect
99
cloud.google.com/go/storage v1.16.0
1010
github.com/Masterminds/semver/v3 v3.1.1
11+
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7
1112
github.com/cyphar/filepath-securejoin v0.2.2
1213
github.com/fluxcd/pkg/apis/meta v0.10.0
13-
github.com/fluxcd/pkg/gittestserver v0.3.0
14+
github.com/fluxcd/pkg/gittestserver v0.4.1
1415
github.com/fluxcd/pkg/gitutil v0.1.0
1516
github.com/fluxcd/pkg/helmtestserver v0.2.0
1617
github.com/fluxcd/pkg/lockedfile v0.1.0

go.sum

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,8 +266,8 @@ github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys=
266266
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
267267
github.com/fluxcd/pkg/apis/meta v0.10.0 h1:N7wVGHC1cyPdT87hrDC7UwCwRwnZdQM46PBSLjG2rlE=
268268
github.com/fluxcd/pkg/apis/meta v0.10.0/go.mod h1:CW9X9ijMTpNe7BwnokiUOrLl/h13miwVr/3abEQLbKE=
269-
github.com/fluxcd/pkg/gittestserver v0.3.0 h1:6aa30mybecBwBWaJ2IEk7pQzefWnjWjxkTSrHMHawvg=
270-
github.com/fluxcd/pkg/gittestserver v0.3.0/go.mod h1:8j36Z6B0BuKNZZ6exAWoyDEpyQoFcjz1IX3WBT7PZNg=
269+
github.com/fluxcd/pkg/gittestserver v0.4.1 h1:knghRrVEEPnpO0VJYjoz0H2YMc4fnKAVt5hDGsB1IHc=
270+
github.com/fluxcd/pkg/gittestserver v0.4.1/go.mod h1:hUPx21fe/6oox336Wih/XF1fnmzLmptNMOvATbTZXNY=
271271
github.com/fluxcd/pkg/gitutil v0.1.0 h1:VO3kJY/CKOCO4ysDNqfdpTg04icAKBOSb3lbR5uE/IE=
272272
github.com/fluxcd/pkg/gitutil v0.1.0/go.mod h1:Ybz50Ck5gkcnvF0TagaMwtlRy3X3wXuiri1HVsK5id4=
273273
github.com/fluxcd/pkg/helmtestserver v0.2.0 h1:cE7YHDmrWI0hr9QpaaeQ0vQ16Z0IiqZKiINDpqdY610=
@@ -984,7 +984,6 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
984984
golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
985985
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
986986
golang.org/x/crypto v0.0.0-20200709230013-948cd5f35899/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
987-
golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
988987
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
989988
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
990989
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=

pkg/git/git.go

Lines changed: 65 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,43 +17,82 @@ limitations under the License.
1717
package git
1818

1919
import (
20+
"bytes"
2021
"context"
22+
"fmt"
23+
"strings"
24+
"time"
2125

22-
"github.com/go-git/go-git/v5/plumbing/transport"
23-
git2go "github.com/libgit2/git2go/v31"
24-
corev1 "k8s.io/api/core/v1"
26+
"github.com/ProtonMail/go-crypto/openpgp"
2527
)
2628

27-
const (
28-
DefaultOrigin = "origin"
29-
DefaultBranch = "master"
30-
DefaultPublicKeyAuthUser = "git"
31-
CAFile = "caFile"
32-
)
29+
type Implementation string
30+
31+
type Hash []byte
3332

34-
type Commit interface {
35-
Verify(secret corev1.Secret) error
36-
Hash() string
33+
// String returns the SHA1 Hash as a string.
34+
func (h Hash) String() string {
35+
return string(h)
3736
}
3837

39-
type CheckoutStrategy interface {
40-
Checkout(ctx context.Context, path, url string, auth *Auth) (Commit, string, error)
38+
type Signature struct {
39+
Name string
40+
Email string
41+
When time.Time
4142
}
4243

43-
type CheckoutOptions struct {
44-
GitImplementation string
45-
RecurseSubmodules bool
44+
type Commit struct {
45+
// Hash is the SHA1 hash of the commit.
46+
Hash Hash
47+
// Reference is the original reference of the commit, for example:
48+
// 'refs/tags/foo'.
49+
Reference string
50+
// Author is the original author of the commit.
51+
Author Signature
52+
// Committer is the one performing the commit, might be different from
53+
// Author.
54+
Committer Signature
55+
// Signature is the PGP signature of the commit.
56+
Signature string
57+
// Encoded is the encoded commit, without any signature.
58+
Encoded []byte
59+
// Message is the commit message, contains arbitrary text.
60+
Message string
4661
}
4762

48-
// TODO(hidde): candidate for refactoring, so that we do not directly
49-
// depend on implementation specifics here.
50-
type Auth struct {
51-
AuthMethod transport.AuthMethod
52-
CABundle []byte
53-
CredCallback git2go.CredentialsCallback
54-
CertCallback git2go.CertificateCheckCallback
63+
// String returns a string representation of the Commit, composed
64+
// out the last part of the Reference element, and/or Hash.
65+
// For example: 'tag-1/a0c14dc8580a23f79bc654faa79c4f62b46c2c22',
66+
// for a "tag-1" tag.
67+
func (c *Commit) String() string {
68+
if short := strings.SplitAfterN(c.Reference, "/", 3); len(short) == 3 {
69+
return fmt.Sprintf("%s/%s", short[2], c.Hash)
70+
}
71+
return fmt.Sprintf("HEAD/%s", c.Hash)
5572
}
5673

57-
type AuthSecretStrategy interface {
58-
Method(secret corev1.Secret) (*Auth, error)
74+
// Verify the Signature of the commit with the given key rings.
75+
// It returns the fingerprint of the key the signature was verified
76+
// with, or an error.
77+
func (c *Commit) Verify(keyRing ...string) (string, error) {
78+
if c.Signature == "" {
79+
return "", fmt.Errorf("commit does not have a PGP signature")
80+
}
81+
82+
for _, r := range keyRing {
83+
reader := strings.NewReader(r)
84+
keyring, err := openpgp.ReadArmoredKeyRing(reader)
85+
if err != nil {
86+
return "", fmt.Errorf("failed to read armored key ring: %w", err)
87+
}
88+
signer, err := openpgp.CheckArmoredDetachedSignature(keyring, bytes.NewBuffer(c.Encoded), bytes.NewBufferString(c.Signature), nil)
89+
if err == nil {
90+
return fmt.Sprintf("%X", signer.PrimaryKey.Fingerprint[12:20]), nil
91+
}
92+
}
93+
return "", fmt.Errorf("failed to verify commit with any of the given key rings")
94+
}
95+
96+
type CheckoutStrategy interface {
97+
Checkout(ctx context.Context, path, url string, config *AuthOptions) (*Commit, error)
5998
}

0 commit comments

Comments
 (0)