projectsyn / commodore

Commodore provides opinionated tenant-aware management of Kapitan inventories and templates. Commodore uses Kapitan for the heavy lifting of rendering templates and resolving a hierachical configuration structure.
https://syn.tools/commodore/
BSD 3-Clause "New" or "Revised" License
46 stars 8 forks source link

Sign commits with GPG in Docker image #579

Closed ccremer closed 2 years ago

ccremer commented 2 years ago

Context

Add support for signing git commits with GPG. In the image we'll need to install GPG as well and the commodore alias probably needs some extra configuration to pass the GPG credentials/agent.

When commit signing with GPG is enabled in the git config, commodore component new <name> fails with an error saying it couldn't sign the commit (see below)

Hacking Commodore and GPG

Here's what I've come up yet. Maybe I'm close to a solution, maybe it's impossible :shrug:

Traceback (most recent call last):...
  File "/usr/local/lib/python3.10/site-packages/commodore/cli.py", line 361, in component_new
    f.create()
  File "/usr/local/lib/python3.10/site-packages/commodore/dependency_templater.py", line 157, in create
    self.commit("Initial commit", amend=want_worktree)
  File "/usr/local/lib/python3.10/site-packages/commodore/dependency_templater.py", line 182, in commit
    repo.commit(msg, amend=amend)
  File "/usr/local/lib/python3.10/site-packages/commodore/gitrepo.py", line 557, in commit
    self._repo.git.execute(  # type: ignore[call-overload]
  File "/usr/local/lib/python3.10/site-packages/git/cmd.py", line 983, in execute
    raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git commit --amend --no-edit --reset-author -m Initial commit
  stderr: 'error: cannot run gpg: No such file or directory
error: gpg failed to sign the data
fatal: failed to write commit object'

When running Commodore with GIT_TRACE=1 env var, we'll get what actually happens when committing:

  File "/usr/local/lib/python3.10/site-packages/commodore/gitrepo.py", line 557, in commit
    self._repo.git.execute(  # type: ignore[call-overload]
  File "/usr/local/lib/python3.10/site-packages/git/cmd.py", line 983, in execute
    raise GitCommandError(redacted_command, status, stderr_value, stdout_value)
git.exc.GitCommandError: Cmd('git') failed due to: exit code(128)
  cmdline: git commit --amend --no-edit --reset-author -m Initial commit
  stderr: '09:51:35.023159 git.c:444               trace: built-in: git commit --amend --no-edit --reset-author -m 'Initial commit'
09:51:35.025208 run-command.c:664       trace: run_command: gpg --status-fd=2 -bsau <GPG ID>
error: gpg failed to sign the data
fatal: failed to write commit object'

Hacked Dockerfile:

FROM projectsyn/commodore:latest

USER 0
RUN apt-get update && apt-get install gpg -y
RUN mkdir /app/.gnupg && chmod ug+w /app/.gnupg
USER 1001

Note: With gpgconf --list-dir agent-socket and gpgconf --list-dir agent-extra-socket we get the paths mentioned below.

Adjusted commodore docker alias:

commodore () {                                                                                                                                                         
        docker run --interactive=true --tty --rm --user="$(id -u)" --env COMMODORE_API_URL --env COMMODORE_API_TOKEN --env SSH_AUTH_SOCK=/tmp/ssh_agent.sock --publish 18000:18000 --volume "${SSH_AUTH_SOCK}:/tmp/ssh_agent.sock" --volume "${HOME}/.ssh/config:/app/.ssh/config:ro" --volume "${HOME}/.ssh/known_hosts:/app/.ssh/known_hosts:ro" --volume "${HOME}/.gitconfig:/app/.gitconfig:ro" --volume "${HOME}/.cache:/app/.cache" --volume "${PWD}:/app/data" --volume "/run/user/1000/gnupg/S.gpg-agent.extra:/app/.gnupg/S.gpg-agent.extra:ro" --volume "/run/user/1000/gnupg/S.gpg-agent:/app/.gnupg/S.gpg-agent:ro" --env GIT_TRACE=1 --workdir /app/data projectsyn/commodore:${COMMODORE_VERSION:=latest}
}

The signing still fails, but when running with --entrypoint /bin/bash -i I can exec into the container and run:

I have no name!@af2c2f54315b:~/data$ gpg --status-fd=2 -bsau <keyid>
gpg: WARNING: unsafe ownership on homedir '/app/.gnupg'
gpg: keybox '/app/.gnupg/pubring.kbx' created
gpg: skipped "<keyid>": No secret key
[GNUPG:] INV_SGNR 9 <keyid>
[GNUPG:] FAILURE sign 17
gpg: signing failed: No secret key

At least the gpg-agent is somewhat recognized

Alternatives

simu commented 2 years ago

According to https://wiki.gnupg.org/AgentForwarding the public key of the signing key needs to be present on the "remote" system (docker container in this case). All the public keys which GnuPG knows about are stored in ~/.gnupg/pubring.kbx. Additionally the wiki page also states that we want to forward the agent extra socket to the server where it should be made available as the regular socket, so the docker alias should be something along the lines of

commodore () {                                                                                                                                                         
  docker run --interactive=true --tty --rm --user="$(id -u)" --env COMMODORE_API_URL \
    --env COMMODORE_API_TOKEN --env SSH_AUTH_SOCK=/tmp/ssh_agent.sock --publish 18000:18000 \
    --volume "${SSH_AUTH_SOCK}:/tmp/ssh_agent.sock" --volume "${HOME}/.ssh/config:/app/.ssh/config:ro" \
    --volume "${HOME}/.ssh/known_hosts:/app/.ssh/known_hosts:ro" --volume "${HOME}/.gitconfig:/app/.gitconfig:ro" \
    --volume "$(gpgconf --list-dir agent-extra-socket):/app/.gnupg/S.gpg-agent:ro" \
    --volume "${HOME}/.gnupg/pubring.kbx:/app/.gnupg/pubring.kbx:ro" \
    --volume "${HOME}/.cache:/app/.cache" --volume "${PWD}:/app/data" \
    --workdir /app/data projectsyn/commodore:${COMMODORE_VERSION:=latest} $*
}

Additionally, we may want to ensure that the alias gracefully degrades for systems which don't have GnuPG installed and setup on the host.

ccremer commented 2 years ago

That was (almost) it!

commodore () {                                                                                                                                                         
  docker run --interactive=true --tty --rm --user="$(id -u)" --env COMMODORE_API_URL \
    --env COMMODORE_API_TOKEN --env SSH_AUTH_SOCK=/tmp/ssh_agent.sock --publish 18000:18000 \
    --volume "${SSH_AUTH_SOCK}:/tmp/ssh_agent.sock" --volume "${HOME}/.ssh/config:/app/.ssh/config:ro" \
    --volume "${HOME}/.ssh/known_hosts:/app/.ssh/known_hosts:ro" --volume "${HOME}/.gitconfig:/app/.gitconfig:ro" \
    --volume "$(gpgconf --list-dir agent-extra-socket):/app/.gnupg/S.gpg-agent:ro" \
    --volume "${HOME}/.gnupg/pubring.kbx:/app/.gnupg/pubring.kbx:ro" \
    --volume "${HOME}/.cache:/app/.cache" --volume "${PWD}:${PWD}" \
    --workdir "${PWD}" projectsyn/commodore:${COMMODORE_VERSION:=latest} $*
}

(change path from /app/data/pubring.kbx:ro to /app/.gnupg/pubring.kbx:ro With this command (and the change of --volume "${PWD}:${PWD}" --workdir "${PWD} I got a new component with signed commit:

$ git log --show-signature
commit 2ec672f12b194fc2a727776681dbe3d3ef626bc1 (HEAD -> master)
gpg: Signature made Mon 15 Aug 2022 01:09:39 PM CEST
gpg:                using RSA key ...
gpg: Good signature from "ccremer ..." [ultimate]
...

How do we want to continue here? Add gpg in the Dockerfile and then just document the 2 additional volumes required for GPG?

simu commented 2 years ago

The path was a typo, I had it working locally.

I'll prepare a PR later today, I want to add a bit of logic to ensure the docker command doesn't break if GnuPG isn't setup on the host system.