Skip to content

Commit 1277492

Browse files
authored
Merge pull request actions#63 from jaredpetersen/master
Added support for GPG signing
2 parents d920b7d + 38e0c8b commit 1277492

File tree

17 files changed

+37572
-137
lines changed

17 files changed

+37572
-137
lines changed

README.md

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -29,27 +29,27 @@ Examples of version specifications that the java-version parameter will accept:
2929
- A major Java version
3030
3131
e.g. ```6, 7, 8, 9, 10, 11, 12, 13, ...```
32-
32+
3333
- A semver Java version specification
3434

3535
e.g. ```8.0.232, 7.0.181, 11.0.4```
36-
36+
3737
e.g. ```8.0.x, >11.0.3, >=13.0.1, <8.0.212```
38-
38+
3939
- An early access (EA) Java version
4040

4141
e.g. ```14-ea, 15-ea```
42-
42+
4343
e.g. ```14.0.0-ea, 15.0.0-ea```
44-
44+
4545
e.g. ```14.0.0-ea.28, 15.0.0-ea.2``` (syntax for specifying an EA build number)
46-
46+
4747
Note that, per semver rules, EA builds will be matched by explicit EA version specifications.
48-
48+
4949
- 1.x syntax
5050

5151
e.g. ```1.8``` (same as ```8```)
52-
52+
5353
e.g. ```1.8.0.212``` (same as ```8.0.212```)
5454

5555

@@ -113,39 +113,60 @@ jobs:
113113
server-id: maven # Value of the distributionManagement/repository/id field of the pom.xml
114114
server-username: MAVEN_USERNAME # env variable for username in deploy
115115
server-password: MAVEN_CENTRAL_TOKEN # env variable for token in deploy
116+
gpg-private-key: ${{ secrets.MAVEN_GPG_PRIVATE_KEY }} # Value of the GPG private key to import
117+
gpg-passphrase: MAVEN_GPG_PASSPHRASE # env variable for GPG private key passphrase
116118
117119
- name: Publish to Apache Maven Central
118-
run: mvn deploy
120+
run: mvn deploy
119121
env:
120122
MAVEN_USERNAME: maven_username123
121123
MAVEN_CENTRAL_TOKEN: ${{ secrets.MAVEN_CENTRAL_TOKEN }}
124+
MAVEN_GPG_PASSPHRASE: ${{ secrets.MAVEN_GPG_PASSPHRASE }}
122125
```
123126

124127
The two `settings.xml` files created from the above example look like the following.
125128

126129
`settings.xml` file created for the first deploy to GitHub Packages
127130
```xml
128-
<servers>
131+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
132+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
133+
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
134+
<servers>
129135
<server>
130136
<id>github</id>
131137
<username>${env.GITHUB_ACTOR}</username>
132138
<password>${env.GITHUB_TOKEN}</password>
133139
</server>
134-
</servers>
140+
<server>
141+
<id>gpg.passphrase</id>
142+
<passphrase>${env.GPG_PASSPHRASE}</passphrase>
143+
</server>
144+
</servers>
145+
</settings>
135146
```
136147

137148
`settings.xml` file created for the second deploy to Apache Maven Central
138149
```xml
139-
<servers>
150+
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
151+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
152+
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
153+
<servers>
140154
<server>
141155
<id>maven</id>
142156
<username>${env.MAVEN_USERNAME}</username>
143157
<password>${env.MAVEN_CENTRAL_TOKEN}</password>
144158
</server>
145-
</servers>
159+
<server>
160+
<id>gpg.passphrase</id>
161+
<passphrase>${env.MAVEN_GPG_PASSPHRASE}</passphrase>
162+
</server>
163+
</servers>
164+
</settings>
146165
```
147166

148-
***NOTE: The `settings.xml` file is created in the Actions $HOME directory. If you have an existing `settings.xml` file at that location, it will be overwritten. See below for using the `settings-path` to change your `settings.xml` file location.***
167+
***NOTE: The `settings.xml` file is created in the Actions $HOME directory. If you have an existing `settings.xml` file at that location, it will be overwritten. See below for using the `settings-path` to change your `settings.xml` file location.***
168+
169+
If `gpg-private-key` input is provided, the private key will be written to a file in the runner's temp directory, the private key file will be imported into the GPG keychain, and then the file will be promptly removed before proceeding with the rest of the setup process. A cleanup step will remove the imported private key from the GPG keychain after the job completes regardless of the job status. This ensures that the private key is no longer accessible on self-hosted runners and cannot "leak" between jobs (hosted runners are always clean instances).
149170

150171
See the help docs on [Publishing a Package](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-apache-maven-for-use-with-github-packages#publishing-a-package) for more information on the `pom.xml` file.
151172

@@ -172,7 +193,7 @@ jobs:
172193
PASSWORD: ${{ secrets.GITHUB_TOKEN }}
173194
```
174195

175-
***NOTE: The `USERNAME` and `PASSWORD` need to correspond to the credentials environment variables used in the publishing section of your `build.gradle`.***
196+
***NOTE: The `USERNAME` and `PASSWORD` need to correspond to the credentials environment variables used in the publishing section of your `build.gradle`.***
176197

177198
See the help docs on [Publishing a Package with Gradle](https://help.github.com/en/github/managing-packages-with-github-packages/configuring-gradle-for-use-with-github-packages#example-using-gradle-groovy-for-a-single-package-in-a-repository) for more information on the `build.gradle` configuration file.
178199

__tests__/auth.test.ts

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ describe('auth tests', () => {
5353
await io.rmRF(altHome);
5454
}, 100000);
5555

56-
it('creates settings.xml with username and password', async () => {
56+
it('creates settings.xml with minimal configuration', async () => {
5757
const id = 'packages';
5858
const username = 'UNAME';
5959
const password = 'TOKEN';
@@ -67,78 +67,84 @@ describe('auth tests', () => {
6767
);
6868
}, 100000);
6969

70-
it('overwrites existing settings.xml files', async () => {
70+
it('creates settings.xml with additional configuration', async () => {
7171
const id = 'packages';
72-
const username = 'USERNAME';
73-
const password = 'PASSWORD';
74-
75-
fs.mkdirSync(m2Dir, {recursive: true});
76-
fs.writeFileSync(settingsFile, 'FAKE FILE');
77-
expect(fs.existsSync(m2Dir)).toBe(true);
78-
expect(fs.existsSync(settingsFile)).toBe(true);
72+
const username = 'UNAME';
73+
const password = 'TOKEN';
74+
const gpgPassphrase = 'GPG';
7975

80-
await auth.configAuthentication(id, username, password);
76+
await auth.configAuthentication(id, username, password, gpgPassphrase);
8177

8278
expect(fs.existsSync(m2Dir)).toBe(true);
8379
expect(fs.existsSync(settingsFile)).toBe(true);
8480
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
85-
auth.generate(id, username, password)
81+
auth.generate(id, username, password, gpgPassphrase)
8682
);
8783
}, 100000);
8884

89-
it('does not create settings.xml without required parameters', async () => {
90-
await auth.configAuthentication('FOO');
91-
92-
expect(fs.existsSync(m2Dir)).toBe(true);
93-
expect(fs.existsSync(settingsFile)).toBe(true);
94-
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
95-
auth.generate('FOO', auth.DEFAULT_USERNAME, auth.DEFAULT_PASSWORD)
96-
);
97-
98-
await auth.configAuthentication(undefined, 'BAR', undefined);
99-
100-
expect(fs.existsSync(m2Dir)).toBe(true);
101-
expect(fs.existsSync(settingsFile)).toBe(true);
102-
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
103-
auth.generate(auth.DEFAULT_ID, 'BAR', auth.DEFAULT_PASSWORD)
104-
);
105-
106-
await auth.configAuthentication(undefined, undefined, 'BAZ');
85+
it('overwrites existing settings.xml files', async () => {
86+
const id = 'packages';
87+
const username = 'USERNAME';
88+
const password = 'PASSWORD';
10789

90+
fs.mkdirSync(m2Dir, {recursive: true});
91+
fs.writeFileSync(settingsFile, 'FAKE FILE');
10892
expect(fs.existsSync(m2Dir)).toBe(true);
10993
expect(fs.existsSync(settingsFile)).toBe(true);
110-
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
111-
auth.generate(auth.DEFAULT_ID, auth.DEFAULT_USERNAME, 'BAZ')
112-
);
11394

114-
await auth.configAuthentication();
95+
await auth.configAuthentication(id, username, password);
11596

11697
expect(fs.existsSync(m2Dir)).toBe(true);
11798
expect(fs.existsSync(settingsFile)).toBe(true);
11899
expect(fs.readFileSync(settingsFile, 'utf-8')).toEqual(
119-
auth.generate(
120-
auth.DEFAULT_ID,
121-
auth.DEFAULT_USERNAME,
122-
auth.DEFAULT_PASSWORD
123-
)
100+
auth.generate(id, username, password)
124101
);
125102
}, 100000);
126103

127-
it('escapes invalid XML inputs', () => {
104+
it('generates valid settings.xml with minimal configuration', () => {
128105
const id = 'packages';
129106
const username = 'USER';
130107
const password = '&<>"\'\'"><&';
131108

132-
expect(auth.generate(id, username, password)).toEqual(`
133-
<settings>
134-
<servers>
135-
<server>
136-
<id>${id}</id>
137-
<username>\${env.${username}}</username>
138-
<password>\${env.&amp;&lt;&gt;&quot;&apos;&apos;&quot;&gt;&lt;&amp;}</password>
139-
</server>
140-
</servers>
141-
</settings>
142-
`);
109+
const expectedSettings = `<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
110+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
111+
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
112+
<servers>
113+
<server>
114+
<id>${id}</id>
115+
<username>\${env.${username}}</username>
116+
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
117+
</server>
118+
</servers>
119+
</settings>`;
120+
121+
expect(auth.generate(id, username, password)).toEqual(expectedSettings);
122+
});
123+
124+
it('generates valid settings.xml with additional configuration', () => {
125+
const id = 'packages';
126+
const username = 'USER';
127+
const password = '&<>"\'\'"><&';
128+
const gpgPassphrase = 'PASSPHRASE';
129+
130+
const expectedSettings = `<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
131+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
132+
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 https://maven.apache.org/xsd/settings-1.0.0.xsd">
133+
<servers>
134+
<server>
135+
<id>${id}</id>
136+
<username>\${env.${username}}</username>
137+
<password>\${env.&amp;&lt;&gt;"''"&gt;&lt;&amp;}</password>
138+
</server>
139+
<server>
140+
<id>gpg.passphrase</id>
141+
<passphrase>\${env.${gpgPassphrase}}</passphrase>
142+
</server>
143+
</servers>
144+
</settings>`;
145+
146+
expect(auth.generate(id, username, password, gpgPassphrase)).toEqual(
147+
expectedSettings
148+
);
143149
});
144150
});

__tests__/gpg.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import path = require('path');
2+
import io = require('@actions/io');
3+
import exec = require('@actions/exec');
4+
5+
jest.mock('@actions/exec', () => {
6+
return {
7+
exec: jest.fn()
8+
};
9+
});
10+
11+
const tempDir = path.join(__dirname, 'runner', 'temp');
12+
process.env['RUNNER_TEMP'] = tempDir;
13+
14+
import gpg = require('../src/gpg');
15+
16+
describe('gpg tests', () => {
17+
beforeEach(async () => {
18+
await io.mkdirP(tempDir);
19+
});
20+
21+
afterAll(async () => {
22+
try {
23+
await io.rmRF(tempDir);
24+
} catch {
25+
console.log('Failed to remove test directories');
26+
}
27+
});
28+
29+
describe('importKey', () => {
30+
it('attempts to import private key and returns null key id on failure', async () => {
31+
const privateKey = 'KEY CONTENTS';
32+
const keyId = await gpg.importKey(privateKey);
33+
34+
expect(keyId).toBeNull();
35+
36+
expect(exec.exec).toHaveBeenCalledWith(
37+
'gpg',
38+
expect.anything(),
39+
expect.anything()
40+
);
41+
});
42+
});
43+
44+
describe('deleteKey', () => {
45+
it('deletes private key', async () => {
46+
const keyId = 'asdfhjkl';
47+
await gpg.deleteKey(keyId);
48+
49+
expect(exec.exec).toHaveBeenCalledWith(
50+
'gpg',
51+
expect.anything(),
52+
expect.anything()
53+
);
54+
});
55+
});
56+
});

__tests__/util.test.ts

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import path = require('path');
2+
3+
const env = process.env;
4+
5+
describe('util tests', () => {
6+
beforeEach(() => {
7+
const tempEnv = Object.assign({}, env);
8+
delete tempEnv.RUNNER_TEMP;
9+
delete tempEnv.USERPROFILE;
10+
process.env = tempEnv;
11+
Object.defineProperty(process, 'platform', {value: 'linux'});
12+
});
13+
14+
describe('getTempDir', () => {
15+
it('gets temp dir using env', () => {
16+
process.env['RUNNER_TEMP'] = 'defaulttmp';
17+
const util = require('../src/util');
18+
19+
const tempDir = util.getTempDir();
20+
21+
expect(tempDir).toEqual(process.env['RUNNER_TEMP']);
22+
});
23+
24+
it('gets temp dir for windows using userprofile', () => {
25+
Object.defineProperty(process, 'platform', {value: 'win32'});
26+
process.env['USERPROFILE'] = 'winusertmp';
27+
const util = require('../src/util');
28+
29+
const tempDir = util.getTempDir();
30+
31+
expect(tempDir).toEqual(
32+
path.join(process.env['USERPROFILE'], 'actions', 'temp')
33+
);
34+
});
35+
36+
it('gets temp dir for windows using c drive', () => {
37+
Object.defineProperty(process, 'platform', {value: 'win32'});
38+
const util = require('../src/util');
39+
40+
const tempDir = util.getTempDir();
41+
42+
expect(tempDir).toEqual(path.join('C:\\', 'actions', 'temp'));
43+
});
44+
45+
it('gets temp dir for mac', () => {
46+
Object.defineProperty(process, 'platform', {value: 'darwin'});
47+
const util = require('../src/util');
48+
49+
const tempDir = util.getTempDir();
50+
51+
expect(tempDir).toEqual(path.join('/Users', 'actions', 'temp'));
52+
});
53+
54+
it('gets temp dir for linux', () => {
55+
const util = require('../src/util');
56+
const tempDir = util.getTempDir();
57+
58+
expect(tempDir).toEqual(path.join('/home', 'actions', 'temp'));
59+
});
60+
});
61+
});

0 commit comments

Comments
 (0)