From 593297d7a220a33c5f8a8200e1f24071f9b051b5 Mon Sep 17 00:00:00 2001 From: Andreas Olsson Date: Wed, 19 Jul 2017 17:30:12 +0200 Subject: [PATCH] Only use `git verify-tag` when verifying annotated tags (#26414) * Only use `git verify-tag` when verifying annotated tags The command `git verify-tag` only applies to annotated tags. When verifying lightweight tags, which are more similar to non-moving branches, one has to use `git verify-commit` instead. Using ':' as a separator is appropriate since that is one of the characters not allowed in a Git reference name. See also https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html * Improve testing of the Git module's gpg verification --- lib/ansible/modules/source_control/git.py | 10 +- .../targets/git/tasks/gpg-verification.yml | 187 ++++++++++++++++++ test/integration/targets/git/tasks/main.yml | 6 +- .../targets/git/tasks/tag-verification.yml | 57 ------ test/integration/targets/git/vars/main.yml | 51 ++++- 5 files changed, 248 insertions(+), 63 deletions(-) create mode 100644 test/integration/targets/git/tasks/gpg-verification.yml delete mode 100644 test/integration/targets/git/tasks/tag-verification.yml diff --git a/lib/ansible/modules/source_control/git.py b/lib/ansible/modules/source_control/git.py index 3624c135a39..424761e9a1c 100644 --- a/lib/ansible/modules/source_control/git.py +++ b/lib/ansible/modules/source_control/git.py @@ -590,15 +590,17 @@ def get_branches(git_path, module, dest): return branches -def get_tags(git_path, module, dest): +def get_annotated_tags(git_path, module, dest): tags = [] - cmd = '%s tag' % (git_path,) + cmd = [git_path, 'for-each-ref', 'refs/tags/', '--format', '%(objecttype):%(refname:short)'] (rc, out, err) = module.run_command(cmd, cwd=dest) if rc != 0: module.fail_json(msg="Could not determine tag data - received %s" % out, stdout=out, stderr=err) for line in to_native(out).split('\n'): if line.strip(): - tags.append(line.strip()) + tagtype, tagname = line.strip().split(':') + if tagtype == 'tag': + tags.append(tagname) return tags @@ -887,7 +889,7 @@ def switch_version(git_path, module, dest, remote, version, verify_commit, depth def verify_commit_sign(git_path, module, dest, version): - if version in get_tags(git_path, module, dest): + if version in get_annotated_tags(git_path, module, dest): git_sub = "verify-tag" else: git_sub = "verify-commit" diff --git a/test/integration/targets/git/tasks/gpg-verification.yml b/test/integration/targets/git/tasks/gpg-verification.yml new file mode 100644 index 00000000000..4fad418f7fa --- /dev/null +++ b/test/integration/targets/git/tasks/gpg-verification.yml @@ -0,0 +1,187 @@ +--- +# Test for verification of GnuPG signatures + +- name: Create GnuPG verification workdir + tempfile: + state: directory + register: git_gpg_workdir + +- name: Define variables based on workdir + set_fact: + git_gpg_keyfile: "{{ git_gpg_workdir.path }}/testkey.asc" + git_gpg_source: "{{ git_gpg_workdir.path }}/source" + git_gpg_dest: "{{ git_gpg_workdir.path }}/dest" + git_gpg_gpghome: "{{ git_gpg_workdir.path }}/gpg" + +- name: Temporary store GnuPG test key + copy: + content: "{{ git_gpg_testkey }}" + dest: "{{ git_gpg_keyfile }}" + +- name: Create temporary GNUPGHOME directory + file: + path: "{{ git_gpg_gpghome }}" + state: directory + mode: 0700 + +- name: Import GnuPG test key + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + command: gpg --import {{ git_gpg_keyfile }} + +- name: Create local GnuPG signed repository directory + file: + path: "{{ git_gpg_source }}" + state: directory + +- name: Generate local GnuPG signed repository + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + shell: | + set -e + git init + touch an_empty_file + git add an_empty_file + git commit --no-gpg-sign --message "Commit, and don't sign" + git tag lightweight_tag/unsigned_commit HEAD + git commit --allow-empty --gpg-sign --message "Commit, and sign" + git tag lightweight_tag/signed_commit HEAD + git tag --annotate --message "This is not a signed tag" unsigned_annotated_tag HEAD + git commit --allow-empty --gpg-sign --message "Commit, and sign" + git tag --sign --message "This is a signed tag" signed_annotated_tag HEAD + git checkout -b some_branch/signed_tip master + git commit --allow-empty --gpg-sign --message "Commit, and sign" + git checkout -b another_branch/unsigned_tip master + git commit --allow-empty --no-gpg-sign --message "Commit, and don't sign" + git checkout master + args: + chdir: "{{ git_gpg_source }}" + +- name: Get hash of an unsigned commit + command: git show-ref --hash --verify refs/tags/lightweight_tag/unsigned_commit + args: + chdir: "{{ git_gpg_source }}" + register: git_gpg_unsigned_commit + +- name: Get hash of a signed commit + command: git show-ref --hash --verify refs/tags/lightweight_tag/signed_commit + args: + chdir: "{{ git_gpg_source }}" + register: git_gpg_signed_commit + +- name: Clone repo and verify signed HEAD + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + verify_commit: yes + +- name: Clone repo and verify a signed lightweight tag + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: lightweight_tag/signed_commit + verify_commit: yes + +- name: Clone repo and verify an unsigned lightweight tag (should fail) + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: lightweight_tag/unsigned_commit + verify_commit: yes + register: git_verify + ignore_errors: yes + +- name: Check that unsigned lightweight tag verification failed + assert: + that: + - git_verify|failed + - git_verify.msg|match("Failed to verify GPG signature of commit/tag.+") + +- name: Clone repo and verify a signed commit + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: "{{ git_gpg_signed_commit.stdout }}" + verify_commit: yes + +- name: Clone repo and verify an unsigned commit + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: "{{ git_gpg_unsigned_commit.stdout }}" + verify_commit: yes + register: git_verify + ignore_errors: yes + +- name: Check that unsigned commit verification failed + assert: + that: + - git_verify|failed + - git_verify.msg|match("Failed to verify GPG signature of commit/tag.+") + +- name: Clone repo and verify a signed annotated tag + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: signed_annotated_tag + verify_commit: yes + +- name: Clone repo and verify an unsigned annotated tag (should fail) + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: unsigned_annotated_tag + verify_commit: yes + register: git_verify + ignore_errors: yes + +- name: Check that unsigned annotated tag verification failed + assert: + that: + - git_verify|failed + - git_verify.msg|match("Failed to verify GPG signature of commit/tag.+") + +- name: Clone repo and verify a signed branch + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: some_branch/signed_tip + verify_commit: yes + +- name: Clone repo and verify an unsigned branch (should fail) + environment: + - GNUPGHOME: "{{ git_gpg_gpghome }}" + git: + repo: "{{ git_gpg_source }}" + dest: "{{ git_gpg_dest }}" + version: another_branch/unsigned_tip + verify_commit: yes + register: git_verify + ignore_errors: yes + +- name: Check that unsigned branch verification failed + assert: + that: + - git_verify|failed + - git_verify.msg|match("Failed to verify GPG signature of commit/tag.+") + +- name: Remove GnuPG verification workdir + file: + path: "{{ git_gpg_workdir.path }}" + state: absent diff --git a/test/integration/targets/git/tasks/main.yml b/test/integration/targets/git/tasks/main.yml index bc87aeb810c..6d776fc6925 100644 --- a/test/integration/targets/git/tasks/main.yml +++ b/test/integration/targets/git/tasks/main.yml @@ -27,7 +27,11 @@ - include: change-repo-url.yml - include: depth.yml - include: checkout-new-tag.yml -- include: tag-verification.yml +- include: gpg-verification.yml + when: > + not gpg_version.stderr and + gpg_version.stdout and + git_version.stdout | version_compare("2.1.0", '>=') - include: localmods.yml - include: reset-origin.yml - include: ambiguous-ref.yml diff --git a/test/integration/targets/git/tasks/tag-verification.yml b/test/integration/targets/git/tasks/tag-verification.yml deleted file mode 100644 index 6bd06b87e1b..00000000000 --- a/test/integration/targets/git/tasks/tag-verification.yml +++ /dev/null @@ -1,57 +0,0 @@ ---- - # Test for tag verification - # clone a repo checkout signed tag, verify tag - -- name: Import Jamie Evans GPG key - command: gpg --keyserver keyserver.ubuntu.com --recv-key 61107C8E - when: > - not gpg_version.stderr and - gpg_version.stdout and - (git_version.stdout | version_compare("2.1.0", '>=') or - gpg_version.stdout | version_compare("1.4.16", '>=')) - -- name: Copy ownertrust - copy: "content='2D55902D66FEEBCEA4447C93E79A36DA61107C8E:6:\n' dest=/tmp/ownertrust-git.txt" - when: > - not gpg_version.stderr and - gpg_version.stdout and - (git_version.stdout | version_compare("2.1.0", '>=') or - gpg_version.stdout | version_compare("1.4.16", '>=')) - -- name: Import ownertrust - command: gpg --import-ownertrust /tmp/ownertrust-git.txt - when: > - not gpg_version.stderr and - gpg_version.stdout and - (git_version.stdout | version_compare("2.1.0", '>=') or - gpg_version.stdout | version_compare("1.4.16", '>=')) - -- name: Clone signed repo and verify tag - git: repo={{ repo_verify }} dest={{ checkout_dir }} version=v0.0 verify_commit=yes - when: > - not gpg_version.stderr and - gpg_version.stdout and - (git_version.stdout | version_compare("2.1.0", '>=') or - gpg_version.stdout | version_compare("1.4.16", '>=')) - -- name: Remove Jamie Evans GPG key - command: gpg --batch --yes --delete-key 61107C8E - when: > - not gpg_version.stderr and - gpg_version.stdout and - (git_version.stdout | version_compare("2.1.0", '>=') or - gpg_version.stdout | version_compare("1.4.16", '>=')) - -- name: Clean up files - file: path="{{ item }}" state=absent - with_items: - - "{{ checkout_dir }}" - - /tmp/ownertrust-git.txt - when: > - not gpg_version.stderr and - gpg_version.stdout and - (git_version.stdout | version_compare("2.1.0", '>=') or - gpg_version.stdout | version_compare("1.4.16", '>=')) - -- name: clear checkout_dir - file: state=absent path={{ checkout_dir }} diff --git a/test/integration/targets/git/vars/main.yml b/test/integration/targets/git/vars/main.yml index f4364bc4814..2c6212fffde 100644 --- a/test/integration/targets/git/vars/main.yml +++ b/test/integration/targets/git/vars/main.yml @@ -12,7 +12,6 @@ repo_submodule1_newer: 'https://github.com/abadger/test_submodules_subm1_newer.g repo_submodule2: 'https://github.com/abadger/test_submodules_subm2.git' repo_update_url_1: 'https://github.com/ansible-test-robinro/git-test-old' repo_update_url_2: 'https://github.com/ansible-test-robinro/git-test-new' -repo_verify: 'https://github.com/pixelrebel/ansible-git-test.git' known_host_files: - "{{ lookup('env','HOME') }}/.ssh/known_hosts" - '/etc/ssh/ssh_known_hosts' @@ -20,3 +19,53 @@ git_version_supporting_depth: 1.9.1 git_version_supporting_ls_remote: 1.7.5 # path to a SSH private key for use with github.com (tests skipped if undefined) # github_ssh_private_key: "{{ lookup('env', 'HOME') }}/.ssh/id_rsa" +git_gpg_testkey: | + -----BEGIN PGP PRIVATE KEY BLOCK----- + + lQOYBFlkmX0BCACtE81Xj/351nnvwnAWMf8ZUP9B1YOPe9ohqNsCQY1DxODVJc9y + ljCoh9fTdoHXuaUMUFistozxCMP81RuZxfbfsGePnl8OAOgWT5Sln6yEG45oClJ0 + RmJJZdDT1lF3VaVwK9NQ5E1oqmk1IOjISi7iFa9TmMn1h7ISP/p+/xtMxQhzUXt8 + APAEhRdc9FfwxaxCHKZBiM7ND+pAm6vpom07ZUgxSppsrXZAxDncTwAeCumDpeOL + LAcSBsw02swOIHFfqHNrkELLr4KJqws+zeAk6R2nq0k16AVdNX+Rb7T3OKmuLawx + HXe8rKpaw0RC+JCogZK4tz0KDNuZPLW2Y5JJABEBAAEAB/4zkKpFk79p35YNskLd + wgCMRN7/+MKNDavUCnBRsEELt0z7BBxVudx+YZaSSITvxj4fuJJqxqqgJ2no2n8y + JdJjG7YHCnqse+WpvAUAAV4PL/ySD704Kj4fOwfoDTrRUIGNNWlseNB9RgQ5UXg5 + MCzeq/JD+En3bnnFySzzCENUcAQfu2FVYgKEiKaKL5Djs6p5w/jTm+Let3EsIczb + ykJ8D4/G/tSrNdp/g10DDy+VclWMhMFqmFesedvytE8jzCVxPKOoRkFTGrX76gIK + eMVxHIYxdCfSTHLjBykMGO9gxfk9lf18roNYs0VV2suyi4fVFxEozSAxwWlwKrXn + 0arvBADPsm5NjlZ5uR06YKbpUUwPTYcwLbasic0qHuUWgNsTVv8dd2il/jbha77m + StU7qRJ1jwbFEFxx7HnTmeGfPbdyKe2qyLJUyD/rpQSC5YirisUchtG8nZsHlnzn + k10SIeB480tkgkdMQx1Eif40aiuQb09/TxaaXAEFKttZhEO4RwQA1VQ8a0IrMBI2 + i4WqaIDNDl3x61JvvFD74v43I0AHKmZUPwcgAd6q2IvCDaKH0hIuBKu6BGq6DPvx + Oc/4r3iRn/xccconxRop2A9ffa00B/eQXrBq+uLBQfyiFL9UfkU8eTAAgbDKRxjY + ScaevoBbbYxkpgJUCL6VnoSdXlbNOO8EAL2ypsVkDmXNgR8ZT8cKSUft47di5T+9 + mhT1qmD62B+D86892y2QAohmUDadYRK9m9WD91Y7gOMeNhYj9qbxyPprPYUL0aPt + L8KS1H73C5WQMOsl2RyIw81asss30LWghsFIJ1gz8gVEjXhV+YC6W9XQ42iabmRR + A67f5sqK1scuO0q0KUFuc2libGUgVGVzdCBSdW5uZXIgPG5vcmVwbHlAZXhhbXBs + ZS5jb20+iQE3BBMBCAAhBQJZZJl9AhsDBQsJCAcCBhUICQoLAgQWAgMBAh4BAheA + AAoJEK0vcLBcXpbYi/kH/R0xk42MFpGd4pndTAsVIjRk/VhmhFc1v6sBeR40GXlt + hyEeOQQnIeHKLhsVT6YnfFZa8b4JwgTD6NeIiibOAlLgaKOWNwZu8toixMPVAzfQ + cRei+/gFXNil0FmBwWreVBDppuIn6XiSEPik0C7eCcw4lD+A+BbL3WGkp+OSQPho + hodIU02hgkrgs/6YJPats8Rgzw9hICsa2j0MjnG6P2z9atMz6tw2SiE5iBl7mZ2Z + zG/HiplleMhf/G8OZOskrWkKiLbpSPfQSKdOFkw1C6yqOlQ+HmuCZ56oyxtpItET + R11uAKt+ABdi4DX3FQQ+A+bGJ1+aKrcorZ8Z8s0XhPo= + =tV71 + -----END PGP PRIVATE KEY BLOCK----- + -----BEGIN PGP PUBLIC KEY BLOCK----- + + mQENBFlkmX0BCACtE81Xj/351nnvwnAWMf8ZUP9B1YOPe9ohqNsCQY1DxODVJc9y + ljCoh9fTdoHXuaUMUFistozxCMP81RuZxfbfsGePnl8OAOgWT5Sln6yEG45oClJ0 + RmJJZdDT1lF3VaVwK9NQ5E1oqmk1IOjISi7iFa9TmMn1h7ISP/p+/xtMxQhzUXt8 + APAEhRdc9FfwxaxCHKZBiM7ND+pAm6vpom07ZUgxSppsrXZAxDncTwAeCumDpeOL + LAcSBsw02swOIHFfqHNrkELLr4KJqws+zeAk6R2nq0k16AVdNX+Rb7T3OKmuLawx + HXe8rKpaw0RC+JCogZK4tz0KDNuZPLW2Y5JJABEBAAG0KUFuc2libGUgVGVzdCBS + dW5uZXIgPG5vcmVwbHlAZXhhbXBsZS5jb20+iQE3BBMBCAAhBQJZZJl9AhsDBQsJ + CAcCBhUICQoLAgQWAgMBAh4BAheAAAoJEK0vcLBcXpbYi/kH/R0xk42MFpGd4pnd + TAsVIjRk/VhmhFc1v6sBeR40GXlthyEeOQQnIeHKLhsVT6YnfFZa8b4JwgTD6NeI + iibOAlLgaKOWNwZu8toixMPVAzfQcRei+/gFXNil0FmBwWreVBDppuIn6XiSEPik + 0C7eCcw4lD+A+BbL3WGkp+OSQPhohodIU02hgkrgs/6YJPats8Rgzw9hICsa2j0M + jnG6P2z9atMz6tw2SiE5iBl7mZ2ZzG/HiplleMhf/G8OZOskrWkKiLbpSPfQSKdO + Fkw1C6yqOlQ+HmuCZ56oyxtpItETR11uAKt+ABdi4DX3FQQ+A+bGJ1+aKrcorZ8Z + 8s0XhPo= + =mUYY + -----END PGP PUBLIC KEY BLOCK-----