Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

input to set private key trust level #168

Merged
merged 2 commits into from May 6, 2023
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
63 changes: 61 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -55,6 +55,7 @@ jobs:
if (!fs.existsSync(gnupgfolder)){
fs.mkdirSync(gnupgfolder);
}
fs.chmodSync(gnupgfolder, '0700');
fs.copyFile('__tests__/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => {
if (err) throw err;
});
Expand All @@ -69,11 +70,11 @@ jobs:
core.setOutput('passphrase', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pass', {encoding: 'utf8'}));
-
name: Import GPG
id: import_gpg
uses: ./
with:
gpg_private_key: ${{ steps.test.outputs.pgp }}
passphrase: ${{ steps.test.outputs.passphrase }}
trust_level: 5
git_config_global: ${{ matrix.global }}
git_user_signingkey: true
git_commit_gpgsign: true
Expand Down Expand Up @@ -116,7 +117,6 @@ jobs:
core.setOutput('passphrase', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pass', {encoding: 'utf8'}));
-
name: Import GPG
id: import_gpg
uses: ./
with:
gpg_private_key: ${{ steps.test.outputs.pgp-base64 }}
Expand All @@ -126,3 +126,62 @@ jobs:
git_tag_gpgsign: true
git_push_gpgsign: if-asked
fingerprint: ${{ matrix.fingerprint }}

trust:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
key:
- test-key
level:
- ''
- 5
- 4
- 3
- 2
- 1
os:
- ubuntu-latest
- macOS-latest
- windows-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: GPG conf
uses: actions/github-script@v6
with:
script: |
const fs = require('fs');
const gnupgfolder = `${require('os').homedir()}/.gnupg`;
if (!fs.existsSync(gnupgfolder)){
fs.mkdirSync(gnupgfolder);
}
fs.chmodSync(gnupgfolder, '0700');
fs.copyFile('__tests__/fixtures/gpg.conf', `${gnupgfolder}/gpg.conf`, (err) => {
if (err) throw err;
});
-
name: Get test key and passphrase
uses: actions/github-script@v6
id: test
with:
script: |
const fs = require('fs');
core.setOutput('pgp', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pgp', {encoding: 'utf8'}));
core.setOutput('passphrase', fs.readFileSync('__tests__/fixtures/${{ matrix.key }}.pass', {encoding: 'utf8'}));
-
name: Import GPG
id: import_gpg
uses: ./
with:
gpg_private_key: ${{ steps.test.outputs.pgp }}
passphrase: ${{ steps.test.outputs.passphrase }}
trust_level: ${{ matrix.level }}
-
name: List trust values
run: |
gpg --export-ownertrust
shell: bash
80 changes: 58 additions & 22 deletions README.md
Expand Up @@ -19,6 +19,7 @@ ___
* [Workflow](#workflow)
* [Sign commits](#sign-commits)
* [Use a subkey](#use-a-subkey)
* [Set key's trust level](#set-keys-trust-level)
* [Customizing](#customizing)
* [inputs](#inputs)
* [outputs](#outputs)
Expand Down Expand Up @@ -76,7 +77,6 @@ jobs:
uses: actions/checkout@v3
-
name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
Expand Down Expand Up @@ -139,7 +139,6 @@ jobs:
uses: actions/checkout@v3
-
name: Import GPG key
id: import_gpg
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
Expand All @@ -164,39 +163,76 @@ sub ed25519 2021-09-24 [S]

You can use the subkey with signing capability whose fingerprint is `C17D11ADF199F12A30A0910F1F80449BE0B08CB8`.

### Set key's trust level

With the `trust_level` input, you can specify the trust level of the GPG key.

Valid values are:
* `1`: unknown
* `2`: never
* `3`: marginal
* `4`: full
* `5`: ultimate

```yaml
name: import-gpg

on:
push:
branches: master

jobs:
import-gpg:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v3
-
name: Import GPG key
uses: crazy-max/ghaction-import-gpg@v5
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
passphrase: ${{ secrets.PASSPHRASE }}
trust_level: 5
```

## Customizing

### inputs

Following inputs can be used as `step.with` keys

| Name | Type | Description |
|---------------------------------------|---------|------------------------------------------------|
| `gpg_private_key` | String | GPG private key exported as an ASCII armored version or its base64 encoding (**required**) |
| `passphrase` | String | Passphrase of the GPG private key |
| `git_config_global` | Bool | Set Git config global (default `false`) |
| `git_user_signingkey` | Bool | Set GPG signing keyID for this Git repository (default `false`) |
| `git_commit_gpgsign` | Bool | Sign all commits automatically. (default `false`) |
| `git_tag_gpgsign` | Bool | Sign all tags automatically. (default `false`) |
| `git_push_gpgsign` | String | Sign all pushes automatically. (default `if-asked`) |
| `git_committer_name` | String | Set commit author's name (defaults to the name associated with the GPG key) |
| `git_committer_email` | String | Set commit author's email (defaults to the email address associated with the GPG key) |
| `workdir` | String | Working directory (below repository root) (default `.`) |
| `fingerprint` | String | Specific fingerprint to use (subkey) |

| Name | Type | Description |
|-----------------------|--------|--------------------------------------------------------------------------------------------|
| `gpg_private_key` | String | GPG private key exported as an ASCII armored version or its base64 encoding (**required**) |
| `passphrase` | String | Passphrase of the GPG private key |
| `trust_level` | String | Set key's trust level |
| `git_config_global` | Bool | Set Git config global (default `false`) |
| `git_user_signingkey` | Bool | Set GPG signing keyID for this Git repository (default `false`) |
| `git_commit_gpgsign` | Bool | Sign all commits automatically. (default `false`) |
| `git_tag_gpgsign` | Bool | Sign all tags automatically. (default `false`) |
| `git_push_gpgsign` | String | Sign all pushes automatically. (default `if-asked`) |
| `git_committer_name` | String | Set commit author's name (defaults to the name associated with the GPG key) |
| `git_committer_email` | String | Set commit author's email (defaults to the email address associated with the GPG key) |
| `workdir` | String | Working directory (below repository root) (default `.`) |
| `fingerprint` | String | Specific fingerprint to use (subkey) |

> **Note**
>
> `git_user_signingkey` needs to be enabled for `git_commit_gpgsign`, `git_tag_gpgsign`,
> `git_push_gpgsign`, `git_committer_name`, `git_committer_email` inputs.
### outputs

Following outputs are available

| Name | Type | Description |
|---------------|---------|---------------------------------------|
| `fingerprint` | String | Fingerprint of the GPG key (recommended as [user ID](https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html)) |
| `keyid` | String | Low 64 bits of the X.509 certificate SHA-1 fingerprint |
| `name` | String | Name associated with the GPG key |
| `email` | String | Email address associated with the GPG key |
| Name | Type | Description |
|---------------|--------|---------------------------------------------------------------------------------------------------------------------------------|
| `fingerprint` | String | Fingerprint of the GPG key (recommended as [user ID](https://www.gnupg.org/documentation/manuals/gnupg/Specify-a-User-ID.html)) |
| `keyid` | String | Low 64 bits of the X.509 certificate SHA-1 fingerprint |
| `name` | String | Name associated with the GPG key |
| `email` | String | Email address associated with the GPG key |

## Contributing

Expand Down
16 changes: 13 additions & 3 deletions __tests__/gpg.test.ts
Expand Up @@ -107,10 +107,10 @@ for (const userInfo of userInfos) {
describe('getKeygrip', () => {
it('returns the keygrip for a given fingerprint', async () => {
await gpg.importKey(userInfo.pgp);
for (const [i, fingerprint] of userInfo.fingerprints.entries()) {
for (const {idx, fingerprint} of userInfo.fingerprints.map((fingerprint, idx) => ({idx, fingerprint}))) {
await gpg.getKeygrip(fingerprint).then(keygrip => {
expect(keygrip.length).toEqual(userInfo.keygrips[i].length);
expect(keygrip).toEqual(userInfo.keygrips[i]);
expect(keygrip.length).toEqual(userInfo.keygrips[idx].length);
expect(keygrip).toEqual(userInfo.keygrips[idx]);
});
}
});
Expand All @@ -128,6 +128,16 @@ for (const userInfo of userInfos) {
});
});

describe('setTrustLevel', () => {
it('set trust level', async () => {
await gpg.importKey(userInfo.pgp);
await gpg.configureAgent(gpg.agentConfig);
expect(() => {
gpg.setTrustLevel(userInfo.keyID, '5');
}).not.toThrow();
});
});

describe('deleteKey', () => {
// eslint-disable-next-line jest/expect-expect
it('removes key from GnuPG', async () => {
Expand Down
3 changes: 3 additions & 0 deletions action.yml
Expand Up @@ -13,6 +13,9 @@ inputs:
passphrase:
description: 'Passphrase of the GPG private key'
required: false
trust_level:
description: "Set key's trust level"
required: false
git_config_global:
description: 'Set Git config global'
default: 'false'
Expand Down
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion dist/index.js.map

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions src/context.ts
Expand Up @@ -3,6 +3,7 @@ import * as core from '@actions/core';
export interface Inputs {
gpgPrivateKey: string;
passphrase: string;
trustLevel: string;
gitConfigGlobal: boolean;
gitUserSigningkey: boolean;
gitCommitGpgsign: boolean;
Expand All @@ -18,6 +19,7 @@ export async function getInputs(): Promise<Inputs> {
return {
gpgPrivateKey: core.getInput('gpg_private_key', {required: true}),
passphrase: core.getInput('passphrase'),
trustLevel: core.getInput('trust_level'),
gitConfigGlobal: core.getBooleanInput('git_config_global'),
gitUserSigningkey: core.getBooleanInput('git_user_signingkey'),
gitCommitGpgsign: core.getBooleanInput('git_commit_gpgsign'),
Expand Down
14 changes: 14 additions & 0 deletions src/gpg.ts
Expand Up @@ -206,6 +206,20 @@ export const presetPassphrase = async (keygrip: string, passphrase: string): Pro
return await gpgConnectAgent(`KEYINFO ${keygrip}`);
};

export const setTrustLevel = async (keyID: string, trust: string): Promise<void> => {
await exec
.getExecOutput('gpg', ['--batch', '--no-tty', '--command-fd', '0', '--edit-key', keyID], {
ignoreReturnCode: true,
silent: true,
input: Buffer.from(`trust\n${trust}\ny\nquit\n`)
})
.then(res => {
if (res.stderr.length > 0 && res.exitCode != 0) {
throw new Error(res.stderr);
}
});
};

export const deleteKey = async (fingerprint: string): Promise<void> => {
await exec
.getExecOutput('gpg', ['--batch', '--yes', '--delete-secret-keys', fingerprint], {
Expand Down
8 changes: 8 additions & 0 deletions src/main.ts
Expand Up @@ -81,6 +81,14 @@ async function run(): Promise<void> {
});
}

if (inputs.trustLevel) {
await core.group(`Setting key's trust level`, async () => {
await gpg.setTrustLevel(privateKey.keyID, inputs.trustLevel).then(() => {
core.info(`Trust level set to ${inputs.trustLevel} for ${privateKey.keyID}`);
});
});
}

await core.group(`Setting outputs`, async () => {
core.info(`fingerprint=${fingerprint}`);
core.setOutput('fingerprint', fingerprint);
Expand Down