Skip to content

Commit

Permalink
feat: Support rage as an alternative age encryption command
Browse files Browse the repository at this point in the history
  • Loading branch information
twpayne committed Jan 24, 2024
1 parent 991ed63 commit efcf32d
Show file tree
Hide file tree
Showing 7 changed files with 114 additions and 58 deletions.
26 changes: 21 additions & 5 deletions .github/workflows/main.yml
Expand Up @@ -13,12 +13,13 @@ env:
AGE_VERSION: 1.1.1
CHOCOLATEY_VERSION: 2.2.2
EDITORCONFIG_CHECKER_VERSION: 2.7.2
FIND_TYPOS_VERSION: 0.0.3
GO_VERSION: 1.21.6
GOFUMPT_VERSION: 0.5.0
GOLANGCI_LINT_VERSION: 1.55.2
GOLINES_VERSION: 0.11.0
GOVERSIONINFO_VERSION: 1.4.0
FIND_TYPOS_VERSION: 0.0.3
RAGE_VERSION: 0.9.2
jobs:
changes:
runs-on: ubuntu-20.04
Expand Down Expand Up @@ -102,10 +103,13 @@ jobs:
go run . --version
- name: install-age
run: |
cd "$(mktemp -d)"
curl -fsSL "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-darwin-amd64.tar.gz" | tar xzf -
sudo install -m 755 age/age /usr/local/bin
sudo install -m 755 age/age-keygen /usr/local/bin
brew install age
age --version
- name: install-rage
run: |
brew tap str4d.xyz/rage https://str4d.xyz/rage
brew install rage
rage --version
- name: install-keepassxc
run: |
brew install keepassxc
Expand Down Expand Up @@ -140,6 +144,12 @@ jobs:
curl -fsSL "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-linux-amd64.tar.gz" | tar xzf -
sudo install -m 755 age/age /usr/local/bin
sudo install -m 755 age/age-keygen /usr/local/bin
- name: install-rage
run: |
cd "$(mktemp -d)"
curl -fsSL "https://github.com/str4d/rage/releases/download/v${RAGE_VERSION}/rage-v${RAGE_VERSION}-x86_64-linux.tar.gz" | tar xzf -
sudo install -m 755 rage/rage /usr/local/bin
sudo install -m 755 rage/rage-keygen /usr/local/bin
- name: test
env:
CHEZMOI_GITHUB_TOKEN: ${{ secrets.CHEZMOI_GITHUB_TOKEN }}
Expand Down Expand Up @@ -225,6 +235,12 @@ jobs:
curl -fsSL "https://github.com/FiloSottile/age/releases/download/v${AGE_VERSION}/age-v${AGE_VERSION}-linux-amd64.tar.gz" | tar xzf -
sudo install -m 755 age/age /usr/local/bin
sudo install -m 755 age/age-keygen /usr/local/bin
- name: install-rage
run: |
cd "$(mktemp -d)"
curl -fsSL "https://github.com/str4d/rage/releases/download/v${RAGE_VERSION}/rage-v${RAGE_VERSION}-x86_64-linux.tar.gz" | tar xzf -
sudo install -m 755 rage/rage /usr/local/bin
sudo install -m 755 rage/rage-keygen /usr/local/bin
- name: build
run: |
go build ./...
Expand Down
4 changes: 2 additions & 2 deletions assets/chezmoi.io/docs/developer/developing-locally.md
Expand Up @@ -33,8 +33,8 @@ $ go test ./...
```

chezmoi's tests include integration tests with other software. If the other
software is not found in `$PATH` the tests will be skipped. Running the full
set of tests requires `age`, `base64`, `bash`, `gpg`, `perl`, `python3`,
software is not found in `$PATH` the tests will be skipped. Running the full set
of tests requires `age`, `base64`, `bash`, `gpg`, `perl`, `python3`, `rage`,
`ruby`, `sed`, `sha256sum`, `unzip`, `xz`, `zip`, and `zstd`.

Run chezmoi:
Expand Down
13 changes: 13 additions & 0 deletions assets/chezmoi.io/docs/user-guide/encryption/rage.md
@@ -0,0 +1,13 @@
# rage

chezmoi supports encrypting files with [rage](https://str4d.xyz/rage).

To use rage, set `age.command` to `rage` in your configuration file, for example:

```toml title="~/.config/chezmoi/chezmoi.toml"
encryption = "age"
[age]
command = "rage"
```

Then, configure chezmoi as you would for [age](age.md).
1 change: 1 addition & 0 deletions assets/chezmoi.io/mkdocs.yml
Expand Up @@ -80,6 +80,7 @@ nav:
- user-guide/encryption/index.md
- age: user-guide/encryption/age.md
- gpg: user-guide/encryption/gpg.md
- rage: user-guide/encryption/rage.md
- Machines:
- General: user-guide/machines/general.md
- Linux: user-guide/machines/linux.md
Expand Down
122 changes: 74 additions & 48 deletions internal/chezmoi/ageencryption_test.go
Expand Up @@ -11,66 +11,83 @@ import (
"github.com/twpayne/chezmoi/v2/internal/chezmoitest"
)

func TestAgeEncryption(t *testing.T) {
command := lookPathOrSkip(t, "age")

identityFile := filepath.Join(t.TempDir(), "chezmoi-test-age-key.txt")
recipient, err := chezmoitest.AgeGenerateKey(identityFile)
assert.NoError(t, err)
var ageCommands = []string{
"age",
"rage",
}

testEncryption(t, &AgeEncryption{
Command: command,
Identity: NewAbsPath(identityFile),
Recipient: recipient,
func TestAgeEncryption(t *testing.T) {
forEachAgeCommand(t, func(t *testing.T, command string) {
t.Helper()

identityFile := filepath.Join(t.TempDir(), "chezmoi-test-age-key.txt")
recipient, err := chezmoitest.AgeGenerateKey(command, identityFile)
assert.NoError(t, err)

testEncryption(t, &AgeEncryption{
Command: command,
Identity: NewAbsPath(identityFile),
Recipient: recipient,
})
})
}

func TestAgeMultipleIdentitiesAndMultipleRecipients(t *testing.T) {
command := lookPathOrSkip(t, "age")

tempDir := t.TempDir()
identityFile1 := filepath.Join(tempDir, "chezmoi-test-age-key1.txt")
recipient1, err := chezmoitest.AgeGenerateKey(identityFile1)
assert.NoError(t, err)
identityFile2 := filepath.Join(tempDir, "chezmoi-test-age-key2.txt")
recipient2, err := chezmoitest.AgeGenerateKey(identityFile2)
assert.NoError(t, err)

testEncryption(t, &AgeEncryption{
Command: command,
Identities: []AbsPath{
NewAbsPath(identityFile1),
NewAbsPath(identityFile2),
},
Recipients: []string{
recipient1,
recipient2,
},
forEachAgeCommand(t, func(t *testing.T, command string) {
t.Helper()

tempDir := t.TempDir()

identityFile1 := filepath.Join(tempDir, "chezmoi-test-age-key1.txt")
recipient1, err := chezmoitest.AgeGenerateKey(command, identityFile1)
assert.NoError(t, err)

identityFile2 := filepath.Join(tempDir, "chezmoi-test-age-key2.txt")
recipient2, err := chezmoitest.AgeGenerateKey(command, identityFile2)
assert.NoError(t, err)

testEncryption(t, &AgeEncryption{
Command: command,
Identities: []AbsPath{
NewAbsPath(identityFile1),
NewAbsPath(identityFile2),
},
Recipients: []string{
recipient1,
recipient2,
},
})
})
}

func TestAgeRecipientsFile(t *testing.T) {
command := lookPathOrSkip(t, "age")
t.Helper()

tempDir := t.TempDir()
identityFile := filepath.Join(tempDir, "chezmoi-test-age-key.txt")
recipient, err := chezmoitest.AgeGenerateKey(identityFile)
assert.NoError(t, err)
recipientsFile := filepath.Join(t.TempDir(), "chezmoi-test-age-recipients.txt")
assert.NoError(t, os.WriteFile(recipientsFile, []byte(recipient), 0o666))
forEachAgeCommand(t, func(t *testing.T, command string) {
t.Helper()

testEncryption(t, &AgeEncryption{
Command: command,
Identity: NewAbsPath(identityFile),
RecipientsFile: NewAbsPath(recipientsFile),
})
tempDir := t.TempDir()

testEncryption(t, &AgeEncryption{
Command: command,
Identity: NewAbsPath(identityFile),
RecipientsFiles: []AbsPath{
NewAbsPath(recipientsFile),
},
identityFile := filepath.Join(tempDir, "chezmoi-test-age-key.txt")
recipient, err := chezmoitest.AgeGenerateKey(command, identityFile)
assert.NoError(t, err)

recipientsFile := filepath.Join(t.TempDir(), "chezmoi-test-age-recipients.txt")
assert.NoError(t, os.WriteFile(recipientsFile, []byte(recipient), 0o666))

testEncryption(t, &AgeEncryption{
Command: command,
Identity: NewAbsPath(identityFile),
RecipientsFile: NewAbsPath(recipientsFile),
})

testEncryption(t, &AgeEncryption{
Command: command,
Identity: NewAbsPath(identityFile),
RecipientsFiles: []AbsPath{
NewAbsPath(recipientsFile),
},
})
})
}

Expand Down Expand Up @@ -129,3 +146,12 @@ func builtinAgeGenerateKey(t *testing.T) (*age.X25519Recipient, AbsPath) {
assert.NoError(t, os.WriteFile(identityFile, []byte(identity.String()), 0o600))
return identity.Recipient(), NewAbsPath(identityFile)
}

func forEachAgeCommand(t *testing.T, f func(*testing.T, string)) {
t.Helper()
for _, command := range ageCommands {
t.Run(command, func(t *testing.T) {
f(t, lookPathOrSkip(t, command))
})
}
}
4 changes: 2 additions & 2 deletions internal/chezmoitest/chezmoitest.go
Expand Up @@ -23,8 +23,8 @@ var ageRecipientRx = regexp.MustCompile(`(?m)^Public key: ([0-9a-z]+)\s*$`)

// AgeGenerateKey generates an identity in identityFile and returns the
// recipient.
func AgeGenerateKey(identityFile string) (string, error) {
cmd := exec.Command("age-keygen", "--output", identityFile)
func AgeGenerateKey(command, identityFile string) (string, error) {
cmd := exec.Command(command+"-keygen", "--output", identityFile) //nolint:gosec
output, err := chezmoilog.LogCmdCombinedOutput(cmd)
if err != nil {
return "", err
Expand Down
2 changes: 1 addition & 1 deletion internal/cmd/main_test.go
Expand Up @@ -306,7 +306,7 @@ func cmdMkAgeConfig(ts *testscript.TestScript, neg bool, args []string) {
homeDir := ts.Getenv("HOME")
ts.Check(os.MkdirAll(homeDir, fs.ModePerm))
identityFile := filepath.Join(homeDir, "key.txt")
recipient, err := chezmoitest.AgeGenerateKey(ts.MkAbs(identityFile))
recipient, err := chezmoitest.AgeGenerateKey("age", ts.MkAbs(identityFile))
ts.Check(err)
configFile := filepath.Join(homeDir, ".config", "chezmoi", "chezmoi.toml")
ts.Check(os.MkdirAll(filepath.Dir(configFile), fs.ModePerm))
Expand Down

0 comments on commit efcf32d

Please sign in to comment.