Skip to content

Conversation

@jaredpetersen
Copy link
Contributor

Adds support for GPG per #43.

I still need to demonstrate a successful test run per the contributor docs, but I figured it would be useful to get this up now.

@jaredpetersen jaredpetersen marked this pull request as draft May 2, 2020 21:36
@jaredpetersen
Copy link
Contributor Author

Two successful matrix test runs:

You can view the List contents step to show the signed files.

@jaredpetersen jaredpetersen marked this pull request as ready for review May 3, 2020 04:13
<activeByDefault>true</activeByDefault>
</activation>
<properties>
<gpg.passphrase>\${env.${gpgPassphrase}}</gpg.passphrase>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

putting this in a server section is more in line with the recommended setup at http://maven.apache.org/plugins/maven-gpg-plugin/usage.html#Configure_passphrase_in_settings.xml and also later on would allow for storing it in an encrypted fashion. Having it in a default profile as property is just too much overhead IMHO.

@jaredpetersen
Copy link
Contributor Author

Updated

Two successful matrix test runs:

View the Output settings step to show the generated settings.xml file.

View the List contents step to show the signed files.

@jaredpetersen
Copy link
Contributor Author

@joshmgross Can you take a look at this PR or let me know who the appropriate reviewer should be? Thanks!

@joshmgross joshmgross self-assigned this May 8, 2020
@joshmgross
Copy link
Contributor

Thanks for the ping @jaredpetersen, I'll take a look at this today.

export const DEFAULT_USERNAME = 'GITHUB_ACTOR';
export const DEFAULT_PASSWORD = 'GITHUB_TOKEN';
export const DEFAULT_GPG_PRIVATE_KEY = '';
export const DEFAULT_GPG_PASSPHRASE = 'GPG_PASSPHRASE';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we default to an empty string here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does default to an empty string. Just like GITHUB_TOKEN and GITHUB_ACTOR are environment variables, GPG_PASSPHRASE is an environment variable as well.

src/auth.ts Outdated
<username>\${env.${escapeXML(username)}}</username>
<password>\${env.${escapeXML(password)}}</password>
</server>
<server>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If a user doesn't specify a GPG key, won't this be empty?

Will that cause issues for users who don't want GPG authentication

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Users will see the following by default whether they want GPG authentication or not:

<settings>
    <servers>
      <server>
        <id>github</id>
        <username>${env.GITHUB_ACTOR}</username>
        <password>${env.GITHUB_TOKEN}</password>
      </server>
      <server>
        <id>gpg.passphrase</id>
        <passphrase>${env.GPG_PASSPHRASE}</passphrase>
      </server>
    </servers>
</settings>

And GPG_PASSPHRASE won't be provided so it will be empty string. This isn't a problem for users that don't want GPG -- maven will behave normally and will only grab information from this other server config if it needs it for the GPG plugin.

@joshmgross
Copy link
Contributor

@clarkbw would you mind taking a look at this?

Copy link
Contributor

@clarkbw clarkbw left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn’t get a chance to test this locally but it looks pretty good.

src/auth.ts Outdated
if (gpgPrivateKey !== DEFAULT_GPG_PRIVATE_KEY) {
console.log('importing gpg key');
const privateKeyDirectory: string = path.join(
os.homedir(),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this is the common default but in a shared runner I think we wanted to allow for paths outside the home directory such that I could drop this into a temp dir.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was worried about that. While the private key is being stored in a "temporary" directory inside the home directory, two setup-java actions running on a shared runner could potentially conflict.

Are you suggesting that we allow users to specify a a temporary private key directory, similar to what is already done with the settings-path option? Or would you rather see setup-java create a temporary directory using os.tmpdir() based on the repository + unique run ID without any prompting. It looks like using $GITHUB_WORKSPACE/.tmp or something is an alternative as well.

I was thinking more along the lines of the latter -- creating a known good temporary directory for the user without asking -- since a user shouldn't care about where the private key file is being written because it's being deleted.

Side note -- this comment about shared runners also brings up the potential need for a temporary key ring. After import, the GPG keyring sticks around and can be used by other runs. This wouldn't be a problem with the hosted runner because it's a new environment every time. A better approach for the shared, self-hosted runners might be to import using a temporary key ring and then clean it up in a post step.

Let me know your thoughts and I can take another crack at this 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a temporary directory, you can use RUNNER_TEMP:

The path of the temporary directory for the runner. This directory is guaranteed to be empty at the start of each job, even on self-hosted runners.
https://help.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions#runner-context

Code example: https://github.com/actions/toolkit/blob/83dd3ef0f1e5bc93c5ab7072e1edf1715a01ba9d/packages/tool-cache/src/tool-cache.ts#L555

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, thanks! I see now that installer.ts is already using this directory as well. Updated PR to use the temp directory but some more improvements can be made since installer.ts apparently already has some logic built in for fallbacks. I'll move that common temp directory logic to a separate file that both auth.ts and installer.ts use in a future commit.


The temporary key ring thing is unfortunately more complicated than I originally thought. You can't just create and use a temporary key ring without also telling the maven gpg plugin to use the same configuration.

If we want to isolate the gpg keyring for shared runners, the cleanest way I've found is to specify a new GPG home directory that lives in the temp directory. This means that each job will get its own brand new GPG configuration that is cleaned up before the next job run.

To start, we will have to import the private key via:

gpg --homedir $RUNNER_TEMP/.gnupg/ --import --batch private.asc

However, using --homedir only affects that particular import command. Future GPG commands will use the default GPG home directory which is a problem for us when it comes invoking GPG later with Maven. To fix this, we have to provide a home directory override for the GPG plugin too.

The resulting settings.xml file would then look like:

<servers>
    <server>
      <id>github</id>
      <username>${env.GITHUB_ACTOR}</username>
      <password>${env.GITHUB_TOKEN}</password>
    </server>
    <server>
      <id>gpg.passphrase</id>
      <passphrase>${env.GPG_PASSPHRASE}</passphrase>
    </server>
    <profiles>
        <profile>
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
            <properties>
                <gpg.homedir>${env.RUNNER_TEMP}</gpg.homedir>
            </properties>
        </profile>
    </profiles>
</servers>

Creating this default GPG home directory override in the Maven settings will break existing setup-java users that have scripted their own GPG import solution. We need to generate a different settings.xml for users that haven't specified a private key:

<servers>
    <server>
      <id>github</id>
      <username>${env.GITHUB_ACTOR}</username>
      <password>${env.GITHUB_TOKEN}</password>
    </server>
</servers>

NOTE: The second gpg.passphrase server section can stay as-is without any harm but we might as well remove it if we're already conditionally generating settings.xml based on parameters.

This is going to be a bit more difficult to implement and it's already late here. I'll get cracking on this when I can.

@jaredpetersen
Copy link
Contributor Author

Still a few kinks to work out it looks like but good progress.

@jaredpetersen jaredpetersen marked this pull request as draft May 12, 2020 23:31
@jaredpetersen
Copy link
Contributor Author

Looks like the homedir approach is a bust as well. Works great everywhere but Windows:

"C:\Program Files\Git\usr\bin\gpg.exe" --homedir d:/a/_temp/.gnupg --import --batch private-key.asc
gpg: directory 'd:/a/_temp/.gnupg' created
gpg: keybox 'd:/a/_temp/.gnupg/pubring.kbx' created
gpg: d:/a/_temp/.gnupg/trustdb.gpg: trustdb created
gpg: key F3D9B27AE556B8C0: public key "Jared Petersen <XXXXXXXXX@gmail.com>" imported
gpg: can't connect to the agent: IPC connect call failed
gpg: error getting the KEK: No agent running
gpg: error reading 'private-key.asc': No agent running
gpg: import from 'private-key.asc' failed: No agent running
gpg: Total number processed: 0
gpg:               imported: 1
gpg:       secret keys read: 1
##[error]The process 'C:\Program Files\Git\usr\bin\gpg.exe' failed with exit code 2

Trying to start up an agent is not going well either.

I'm going to keep working on this. I think it's valuable for us to clean up the keys we import so that we don't leave anything behind. We'll just have to do it a different way.

Closing this out for now so that I can work on this without pinging everyone constantly.

@AirQuick
Copy link

AirQuick commented May 13, 2020

C:\Users\MyUser>"%ProgramFiles%\Git\usr\bin\gpg.exe" --version
gpg (GnuPG) 2.2.20-unknown
...
Home: /c/Users/MyUser/.gnupg

This seems to suggest dropping : and prepending /, which I tried:

C:\Users\MyUser>tskill gpg-agent
Could not find process: gpg-agent

C:\Users\MyUser>dir c:\Users\MyUser\AppData\Local\Temp\.gnupg /b

C:\Users\MyUser>"%ProgramFiles%\Git\usr\bin\gpg.exe" --homedir "/c/Users/MyUser/AppData/Local/Temp/.gnupg" --import --batch "C:\Users\MyUser\Desktop\gpg-private.key.backup"
gpg: keybox '/c/Users/MyUser/AppData/Local/Temp/.gnupg/pubring.kbx' created
gpg: /c/Users/MyUser/AppData/Local/Temp/.gnupg/trustdb.gpg: trustdb created
gpg: key ...: public key "..." imported
gpg: key ...: secret key imported
gpg: Total number processed: 1
gpg:               imported: 1
gpg:       secret keys read: 1
gpg:   secret keys imported: 1

C:\Users\MyUser>dir c:\Users\MyUser\AppData\Local\Temp\.gnupg /b
private-keys-v1.d
pubring.kbx
pubring.kbx~
trustdb.gpg

C:\Users\MyUser>tskill gpg-agent

C:\Users\MyUser>

@jaredpetersen
Copy link
Contributor Author

@AirQuick That's a great find. So you didn't have to start up a new GPG agent?

@AirQuick
Copy link

I didn't have to. The first tskill gpg-agent in my example meant to ensure it.

@clarkbw
Copy link
Contributor

clarkbw commented May 13, 2020

Also worth noting that this setup-java didn't require it because we stored ENV variables but it is possible to run a cleanup script afterward if you have keys or secrets that should be removed.

Checkout this example:

@jaredpetersen
Copy link
Contributor Author

@clarkbw Yeah, I was thinking of that as an alternative solution since the homedir approach wasn't working very well on Windows. You could use the default homedir to import the key, parse out the key ID from the output, and then remove the key in a cleanup step.

The advantage of that is that approach is that your job can take advantage of other keys that were set up prior on the shared runner. It's probably a healthier approach in the longer term.

Sorry for all the back and forth on this. I use GitHub actions in a hosted-only context so learning about and wrapping my head around the shared paradigm has been a challenge.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants