Skip to content
This repository was archived by the owner on Nov 1, 2022. It is now read-only.

Git commit signing #1394

Merged
merged 19 commits into from
Mar 4, 2019
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
2 changes: 2 additions & 0 deletions chart/flux/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,11 +207,13 @@ The following tables lists the configurable parameters of the Weave Flux chart a
| `git.user` | `Weave Flux` | Username to use as git committer
| `git.email` | `support@weave.works` | Email to use as git committer
| `git.setAuthor` | `false` | If set, the author of git commits will reflect the user who initiated the commit and will differ from the git committer.
| `git.signingKey` | `None` | If set, commits will be signed with this GPG key
| `git.label` | `flux-sync` | Label to keep track of sync progress, used to tag the Git branch
| `git.ciSkip` | `false` | Append "[ci skip]" to commit messages so that CI will skip builds
| `git.pollInterval` | `5m` | Period at which to poll git repo for new commits
| `git.timeout` | `20s` | Duration after which git operations time out
| `git.secretName` | `None` | Kubernetes secret with the SSH private key. Superceded by `helmOperator.git.secretName` if set.
| `gpgKeys.secretName` | `None` | Kubernetes secret with GPG keys the Flux daemon should import
| `ssh.known_hosts` | `None` | The contents of an SSH `known_hosts` file, if you need to supply host key(s)
| `registry.pollInterval` | `5m` | Period at which to check for updated images
| `registry.rps` | `200` | Maximum registry requests per second per host
Expand Down
16 changes: 16 additions & 0 deletions chart/flux/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ spec:
- name: docker-credentials
secret:
secretName: "{{ .Values.registry.dockercfg.secretName }}"
{{- end }}
{{- if .Values.gpgKeys.secretName }}
- name: gpg-keys
secret:
secretName: {{ .Values.gpgKeys.secretName }}
defaultMode: 0400
{{- end }}
containers:
Expand Down Expand Up @@ -95,6 +100,11 @@ spec:
mountPath: /dockercfg/
readOnly: true
{{- end }}
{{- if .Values.gpgKeys.secretName }}
- name: gpg-keys
mountPath: /root/gpg-import
readOnly: true
{{- end }}
env:
- name: KUBECONFIG
value: /root/.kubectl/config
Expand All @@ -113,6 +123,12 @@ spec:
- --git-path={{ .Values.git.path }}
- --git-user={{ .Values.git.user }}
- --git-email={{ .Values.git.email }}
{{- if .Values.gpgKeys.secretName }}
- --git-gpg-key-import=/root/gpg-import
{{- end }}
{{- if .Values.git.signingKey }}
- --git-signing-key={{ .Values.git.signingKey }}
{{- end }}
- --git-set-author={{ .Values.git.setAuthor }}
- --git-poll-interval={{ .Values.git.pollInterval }}
- --git-timeout={{ .Values.git.timeout }}
Expand Down
6 changes: 6 additions & 0 deletions chart/flux/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ tolerations: []

affinity: {}

gpgKeys:
# These keys will be imported into GPG in the Flux container.
secretName: ""

git:
# URL of git repo with Kubernetes manifests; e.g. git.url=ssh://git@github.com/weaveworks/flux-get-started
url: ""
Expand All @@ -106,6 +110,8 @@ git:
user: "Weave Flux"
# Email to use as git committer
email: "support@weave.works"
# If set, commits will be signed with this GPG key.
signingKey: ""
# If set, the author of git commits will reflect the user who initiated the commit and will differ from the git committer.
setAuthor: false
# Label to keep track of sync progress
Expand Down
18 changes: 18 additions & 0 deletions cmd/fluxd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/weaveworks/flux/cluster/kubernetes"
"github.com/weaveworks/flux/daemon"
"github.com/weaveworks/flux/git"
"github.com/weaveworks/flux/gpg"
transport "github.com/weaveworks/flux/http"
"github.com/weaveworks/flux/http/client"
daemonhttp "github.com/weaveworks/flux/http/daemon"
Expand Down Expand Up @@ -97,6 +98,10 @@ func main() {
gitPollInterval = fs.Duration("git-poll-interval", 5*time.Minute, "period at which to poll git repo for new commits")
gitTimeout = fs.Duration("git-timeout", 20*time.Second, "duration after which git operations time out")

// GPG commit signing
gitImportGPG = fs.String("git-gpg-key-import", "", "keys at the path given (either a file or a directory) will be imported for use in signing commits")
gitSigningKey = fs.String("git-signing-key", "", "if set, commits will be signed with this GPG key")

// syncing
syncInterval = fs.Duration("sync-interval", 5*time.Minute, "apply config in git to cluster at least this often, even if there are no new commits")
syncGC = fs.Bool("sync-garbage-collection", false, "experimental; delete resources that were created by fluxd, but are no longer in the git repo")
Expand Down Expand Up @@ -190,6 +195,17 @@ func main() {
*sshKeygenDir = *k8sSecretVolumeMountPath
}

// Import GPG keys, if we've been told where to look for them
if *gitImportGPG != "" {
keyfiles, err := gpg.ImportKeys(*gitImportGPG)
if err != nil {
logger.Log("error", "failed to import GPG keys", "err", err.Error())
}
if keyfiles != nil {
logger.Log("info", "imported GPG keys", "files", fmt.Sprintf("%v", keyfiles))
}
}

// Mechanical components.

// When we can receive from this channel, it indicates that we
Expand Down Expand Up @@ -423,6 +439,7 @@ func main() {
NotesRef: *gitNotesRef,
UserName: *gitUser,
UserEmail: *gitEmail,
SigningKey: *gitSigningKey,
SetAuthor: *gitSetAuthor,
SkipMessage: *gitSkipMessage,
}
Expand All @@ -442,6 +459,7 @@ func main() {
"url", *gitURL,
"user", *gitUser,
"email", *gitEmail,
"signing-key", *gitSigningKey,
"sync-tag", *gitSyncTag,
"notes-ref", *gitNotesRef,
"set-author", *gitSetAuthor,
Expand Down
10 changes: 8 additions & 2 deletions daemon/daemon.go
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,10 @@ func (d *Daemon) updatePolicy(spec update.Spec, updates policy.Updates) updateFu
if d.GitConfig.SetAuthor {
commitAuthor = spec.Cause.User
}
commitAction := git.CommitAction{Author: commitAuthor, Message: policyCommitMessage(updates, spec.Cause)}
commitAction := git.CommitAction{
Author: commitAuthor,
Message: policyCommitMessage(updates, spec.Cause),
}
if err := working.CommitAndPush(ctx, commitAction, &note{JobID: jobID, Spec: spec}); err != nil {
// On the chance pushing failed because it was not
// possible to fast-forward, ask for a sync so the
Expand Down Expand Up @@ -464,7 +467,10 @@ func (d *Daemon) release(spec update.Spec, c release.Changes) updateFunc {
if d.GitConfig.SetAuthor {
commitAuthor = spec.Cause.User
}
commitAction := git.CommitAction{Author: commitAuthor, Message: commitMsg}
commitAction := git.CommitAction{
Author: commitAuthor,
Message: commitMsg,
}
if err := working.CommitAndPush(ctx, commitAction, &note{JobID: jobID, Spec: spec, Result: result}); err != nil {
// On the chance pushing failed because it was not
// possible to fast-forward, ask the repo to fetch
Expand Down
6 changes: 5 additions & 1 deletion daemon/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,11 @@ func (d *Daemon) doSync(logger log.Logger, lastKnownSyncTagRev *string, warnedAb
if oldTagRev != newTagRev {
{
ctx, cancel := context.WithTimeout(ctx, d.GitOpTimeout)
err := working.MoveSyncTagAndPush(ctx, newTagRev, "Sync pointer")
tagAction := git.TagAction{
Revision: newTagRev,
Message: "Sync pointer",
}
err := working.MoveSyncTagAndPush(ctx, tagAction)
cancel()
if err != nil {
return err
Expand Down
12 changes: 10 additions & 2 deletions daemon/loop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,11 @@ func TestDoSync_NoNewCommits(t *testing.T) {
err := d.WithClone(ctx, func(co *git.Checkout) error {
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
return co.MoveSyncTagAndPush(ctx, "HEAD", "Sync pointer")
tagAction := git.TagAction{
Revision: "HEAD",
Message: "Sync pointer",
}
return co.MoveSyncTagAndPush(ctx, tagAction)
})
if err != nil {
t.Fatal(err)
Expand Down Expand Up @@ -220,7 +224,11 @@ func TestDoSync_WithNewCommit(t *testing.T) {
defer cancel()

var err error
err = checkout.MoveSyncTagAndPush(ctx, "HEAD", "Sync pointer")
tagAction := git.TagAction{
Revision: "HEAD",
Message: "Sync pointer",
}
err = checkout.MoveSyncTagAndPush(ctx, tagAction)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile.flux
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ FROM alpine:3.6

WORKDIR /home/flux

RUN apk add --no-cache openssh ca-certificates tini 'git>=2.3.0'
RUN apk add --no-cache openssh ca-certificates tini 'git>=2.3.0' gnupg

# Add git hosts to known hosts file so we can use
# StrickHostKeyChecking with git+ssh
Expand Down
84 changes: 84 additions & 0 deletions git/gittest/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package gittest

import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sync"
Expand All @@ -12,6 +13,7 @@ import (

"github.com/weaveworks/flux/cluster/kubernetes/testfiles"
"github.com/weaveworks/flux/git"
"github.com/weaveworks/flux/gpg/gpgtest"
)

type Note struct {
Expand Down Expand Up @@ -66,6 +68,88 @@ func TestCommit(t *testing.T) {
}
}

func TestSignedCommit(t *testing.T) {
gpgHome, signingKey, gpgCleanup := gpgtest.GPGKey(t)
defer gpgCleanup()

config := TestConfig
config.SigningKey = signingKey

os.Setenv("GNUPGHOME", gpgHome)
defer os.Unsetenv("GNUPGHOME")

checkout, repo, cleanup := CheckoutWithConfig(t, config)
defer cleanup()

for file, _ := range testfiles.Files {
dirs := checkout.ManifestDirs()
path := filepath.Join(dirs[0], file)
if err := ioutil.WriteFile(path, []byte("FIRST CHANGE"), 0666); err != nil {
t.Fatal(err)
}
break
}

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

commitAction := git.CommitAction{Message: "Changed file"}
if err := checkout.CommitAndPush(ctx, commitAction, nil); err != nil {
t.Fatal(err)
}

err := repo.Refresh(ctx)
if err != nil {
t.Error(err)
}

commits, err := repo.CommitsBefore(ctx, "HEAD")

if err != nil {
t.Fatal(err)
}
if len(commits) < 1 {
t.Fatal("expected at least one commit")
}
expectedKey := signingKey[len(signingKey)-16:]
foundKey := commits[0].SigningKey[len(commits[0].SigningKey)-16:]
if expectedKey != foundKey {
t.Errorf(`expected commit signing key to be:
%s

but it was

%s
`, expectedKey, foundKey)
}
}
func TestSignedTag(t *testing.T) {
gpgHome, signingKey, gpgCleanup := gpgtest.GPGKey(t)
defer gpgCleanup()

config := TestConfig
config.SigningKey = signingKey

os.Setenv("GNUPGHOME", gpgHome)
defer os.Unsetenv("GNUPGHOME")

checkout, _, cleanup := CheckoutWithConfig(t, config)
defer cleanup()

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

tagAction := git.TagAction{Revision: "HEAD", Message: "Sync pointer"}
if err := checkout.MoveSyncTagAndPush(ctx, tagAction); err != nil {
t.Fatal(err)
}

err := checkout.VerifySyncTag(ctx)
if err != nil {
t.Fatal(err)
}
}

func TestCheckout(t *testing.T) {
repo, cleanup := Repo(t)
defer cleanup()
Expand Down
Loading