Commit ad7aeb0d authored by Bragolgirith's avatar Bragolgirith
Browse files

feat(release): support Git commit signing and custom Git user name and email

parent 644779c0
Loading
Loading
Loading
Loading
+45 −1
Original line number Diff line number Diff line
@@ -267,6 +267,10 @@ They are bound to the `publish` stage, and use the following variables:
| `release-scm-release-comment` / `MAVEN_RELEASE_SCM_RELEASE_COMMENT` | Maven release plugin [scmReleaseCommitComment](https://maven.apache.org/maven-release/maven-release-plugin/prepare-mojo.html#scmReleaseCommitComment) parameter (since Maven `3.0.0-M1`) | _none_ (Maven default) |
| `release-scm-dev-comment` / `MAVEN_RELEASE_SCM_DEV_COMMENT` | Maven release plugin [scmDevelopmentCommitComment](https://maven.apache.org/maven-release/maven-release-plugin/prepare-mojo.html#scmDevelopmentCommitComment) parameter (since Maven `3.0.0-M1`) | _none_ (Maven default) |
| `mvn-semrel-release-disabled` / `MVN_SEMREL_RELEASE_DISABLED` | Set to `true` to disable [semantic-release integration](#semantic-release-integration)   | _none_ (disabled) |
| `GIT_USER_NAME` | Git user name to use when committing (global TBC variable) | `$GITLAB_USER_NAME` |
| `GIT_USER_EMAIL` | Git user email to use when committing (global TBC variable) | `$GITLAB_USER_EMAIL` |
| :lock: `GIT_SIGNING_KEY_GPG` | Git GPG signing key to use for commit signing (global TBC variable) | _none_ (disabled) |
| :lock: `GIT_SIGNING_KEY_SSH` | Git SSH signing key to use for commit signing (global TBC variable) | _none_ (disabled) |

More info:

@@ -426,6 +430,8 @@ We recommend you to use a [project deploy key](https://docs.gitlab.com/user/proj

The key should not have a passphrase (see [how to generate a new SSH key pair](https://docs.gitlab.com/user/ssh/#generate-an-ssh-key-pair)).

Note: It is strongly recommended to use a Service Account user (GitLab Premium feature), rather than a human user, to create the deploy key. This helps avoid the risks and limitations outlined in the [Security implications](https://docs.gitlab.com/user/project/deploy_keys/#security-implications) section.

Specify :lock: `$GIT_PRIVATE_KEY` as protected project variable with the private part of the deploy key.

```PEM
@@ -448,7 +454,7 @@ The template handles both classic variable and file variable.

##### Using Git user authentication

Simply specify :lock: `$GIT_USERNAME` and :lock: `$GIT_PASSWORD` as protected project variables and they will be dynamically 
Simply specify :lock: `$GIT_USER_NAME` and :lock: `$GIT_PASSWORD` as protected project variables, and they will be dynamically 
evaluated and appended to the Maven release arguments.

Note that the password should be an access token with `write_repository` scope and `Maintainer` role.
@@ -463,6 +469,44 @@ Note that the password should be an access token with `write_repository` scope a
  </scm>
```

#### Working around Push Rules

A Maven release involves some Git commit and push operations, which might fail in projects where one or more [push rules](https://docs.gitlab.com/user/project/repository/push_rules/) have been activated.

##### "Reject unverified users" and "Reject inconsistent user name" push rules

With these rules enabled, the Git push will fail unless the commit author name and committer email match the name and email of the user that _authenticated the push_.
* If an **SSH key** is used, this will be the user associated with that SSH key.
* If a **deploy key** is used, this will be the user that created the deploy key.
* If a **deploy token** is used, this will be the deploy token bot user.

During a Maven release, however, the Git user name and email will be derived from the user that _triggered the release job_ instead, which may or may not be the same user.

To work around this, you can explicitly set the `$GIT_USER_NAME` and `$GIT_USER_EMAIL` variables to the appropriate user's name and email.

##### "Reject unsigned commits" push rule

With this rule enabled, the Git push will fail unless all commits are signed.

While GitLab supports commit signing via GPG, SSH, and X.509, the Maven template currently only supports GPG and SSH.

###### Automatic commit signing using GPG

To enable automatic commit signing using GPG, you must generate a GPG keypair, add its public key to the appropriate user, and configure its private key as the :lock: `$GIT_SIGNING_KEY_GPG` protected project variable.

###### Automatic commit signing using SSH

To enable automatic commit signing using SSH, you must generate an SSH keypair, add its public key to the appropriate user (with usage type `Signing`), and configure its private key as the :lock: `$GIT_SIGNING_KEY_SSH` protected project variable.

:warning: SSH commit signing requires Git version 2.34 or higher in your build image.

Note: If you are already using SSH key authentication using `$GIT_PRIVATE_KEY`, the same SSH key can be used for commit signing, provided its usage type was set to `Authentication & Signing`. You can configure this as follows:

```yaml
variables:
  GIT_SIGNING_KEY_SSH: "${GIT_PRIVATE_KEY}"
```

## Variants

### Jib variant
+12 −0
Original line number Diff line number Diff line
@@ -202,6 +202,18 @@
          "description": "Git private SSH key (if you wish to release using SSH key or GitLab Deploy Key)",
          "secret": true
        },
        {
          "name": "GIT_SIGNING_KEY_GPG",
          "description": "Git GPG signing key to use for commit signing (global TBC variable)",
          "advanced": true,
          "secret": true
        },
        {
          "name": "GIT_SIGNING_KEY_SSH",
          "description": "Git SSH signing key to use for commit signing (global TBC variable)",
          "advanced": true,
          "secret": true
        },
        {
          "name": "MAVEN_REPOSITORY_USERNAME",
          "description": "Maven repository username (inject in your settings.xml as ${env.MAVEN_REPOSITORY_USERNAME})",
+69 −7
Original line number Diff line number Diff line
@@ -314,20 +314,29 @@ stages:
    fi
  }

  # ensures an SSH agent is running
  function start_ssh_agent() {
    if [[ -z "${SSH_AUTH_SOCK}" ]]; then
      log_info "Starting SSH agent..."
      eval "$(ssh-agent -s)"
    fi
  }
  
  function configure_scm_auth() {
    log_info "Preparing Git repository for release..."
    # set user info
    git config --global user.email "${GITLAB_USER_EMAIL}"
    git config --global user.name "${GITLAB_USER_NAME}"
    git config --global user.email "${GIT_USER_EMAIL:-$GITLAB_USER_EMAIL}"
    # shellcheck disable=SC2153
    git config --global user.name "${GIT_USER_NAME:-${GIT_USERNAME:-$GITLAB_USER_NAME}}"
    # shellcheck disable=SC2086
    scm_url=$(mvn $MAVEN_CLI_OPTS $mvn_settings_opt $java_proxy_args help:evaluate -Dexpression=project.scm.developerConnection -q -DforceStdout | tail -n 1)
    if [[ $scm_url == "scm:git:https"* ]]; then
      if [[ -n "${GIT_USERNAME}" ]] && [[ -n "${GIT_PASSWORD}" ]]; then
        log_info "--- using SCM credentials from env (\$GIT_USERNAME and \$GIT_PASSWORD)..."
        export scm_auth_args="-Dusername=${GIT_USERNAME} -Dpassword=${GIT_PASSWORD}"
      if [[ -n "${GIT_USER_NAME:-$GIT_USERNAME}" ]] && [[ -n "${GIT_PASSWORD}" ]]; then
        log_info "--- using SCM credentials from env (\$GIT_USER_NAME and \$GIT_PASSWORD)..."
        export scm_auth_args="-Dusername=${GIT_USER_NAME:-$GIT_USERNAME} -Dpassword=${GIT_PASSWORD}"
      else
        log_error "--- project scm.developerConnection is using HTTPS protocol but no Git credentials are configured."
        log_error "--- Please specify the \$GIT_USERNAME and \$GIT_PASSWORD variables or change to SSH protocol with a SSH key."
        log_error "--- Please specify the \$GIT_USER_NAME and \$GIT_PASSWORD variables or change to SSH protocol with a SSH key."
      fi
    else
      # assume project is using SSH protocol
@@ -335,7 +344,7 @@ stages:
        log_info "--- using Git SSH key from env (\$GIT_PRIVATE_KEY)..."
        mkdir -m 700 "${HOME}/.ssh"
        ssh-keyscan -H "${CI_SERVER_HOST}" >> ~/.ssh/known_hosts
        eval "$(ssh-agent -s)"
        start_ssh_agent
        # Handle file variable
        if [[ -f "${GIT_PRIVATE_KEY}" ]]; then
          tr -d '\r' < "${GIT_PRIVATE_KEY}" | ssh-add -
@@ -349,6 +358,58 @@ stages:
    fi
  }
  
  function configure_commit_signing() {
    log_info "Preparing Git for commit signing..."
    if [[ -n "${GIT_SIGNING_KEY_GPG}" ]]; then
      log_info "--- using GPG signing key from env (\$GIT_SIGNING_KEY_GPG)..."
      if [[ ! -d "${HOME}/.gnupg" ]]; then
        log_info "creating GPG base configuration"
        gpg -k
      fi
      # Import the key and extract its ID from the command output
      local gpg_key_id
      if [[ -f "${GIT_SIGNING_KEY_GPG}" ]]; then
        # Handle file variable
        if ! gpg --batch --dry-run --yes --import "${GIT_SIGNING_KEY_GPG}"; then
          fail "Could not import GPG key."
        fi
        gpg_key_id=$(gpg --batch --yes --import "${GIT_SIGNING_KEY_GPG}" 2>&1 | grep "key [A-F0-9]" | head -n 1 | sed -e 's/^.*key \([A-F0-9]*\): .*$/\1/g')
      else
        # Handle traditional variable
        if ! echo "$GIT_SIGNING_KEY_GPG" | gpg --batch --dry-run --yes --import -; then
          fail "Could not import GPG key."
        fi
        gpg_key_id=$(echo "$GIT_SIGNING_KEY_GPG" | gpg --batch --yes --import - 2>&1 | grep "key [A-F0-9]" | head -n 1 | sed -e 's/^.*key \([A-F0-9]*\): .*$/\1/g')
      fi
      if [[ -z "${gpg_key_id}" ]]; then
          fail "Could not extract key ID from gpg --import command."
      fi
      git config --global commit.gpgsign true
      git config --global user.signingkey "${gpg_key_id}"
    elif [[ -n "${GIT_SIGNING_KEY_SSH}" ]]; then
      log_info "--- using SSH signing key from env (\$GIT_SIGNING_KEY_SSH)..."
      start_ssh_agent
      # Derive the public key from the private key
      local signing_public_key
      if [[ -f "${GIT_SIGNING_KEY_SSH}" ]]; then
        # Handle file variable
        signing_public_key=$(tr -d '\r' < "${GIT_SIGNING_KEY_SSH}" | ssh-keygen -y -f /dev/stdin)
      else
        # Handle traditional variable
        signing_public_key=$(echo "${GIT_SIGNING_KEY_SSH}" | tr -d '\r' | ssh-keygen -y -f /dev/stdin)
      fi
      if [[ -z "${signing_public_key}" ]]; then
        fail "Could not derive SSH public key."
      fi
      # Configure automatic commit signing (git >= 2.34 required for ssh)
      git config --global gpg.format ssh
      git config --global user.signingkey "key::${signing_public_key}"
      git config --global commit.gpgsign true
    else
      log_info "--- no signing key is configured; commits will not be signed."
    fi
  }

  function install_ca_certs() {
    certs=$1
    if [[ -z "$certs" ]]
@@ -808,6 +869,7 @@ mvn-release:
    - git checkout -B "$CI_COMMIT_REF_NAME"
  script:
    - configure_scm_auth
    - configure_commit_signing
    - maybe_set_version_from_git
    - |
      if [ "${SEMREL_INFO_ON}" ] && [ "${MVN_SEMREL_RELEASE_DISABLED}" != "true" ]