signed push: teach smart-HTTP to pass "git push --signed" around
authorJunio C Hamano <gitster@pobox.com>
Mon, 15 Sep 2014 21:59:00 +0000 (14:59 -0700)
committerJunio C Hamano <gitster@pobox.com>
Wed, 17 Sep 2014 21:58:04 +0000 (14:58 -0700)
The "--signed" option received by "git push" is first passed to the
transport layer, which the native transport directly uses to notice
that a push certificate needs to be sent. When the transport-helper
is involved, however, the option needs to be told to the helper with
set_helper_option(), and the helper needs to take necessary action.
For the smart-HTTP helper, the "necessary action" involves spawning
the "git send-pack" subprocess with the "--signed" option.

Once the above all gets wired in, the smart-HTTP transport now can
use the push certificate mechanism to authenticate its pushes.

Add a test that is modeled after tests for the native transport in
t5534-push-signed.sh to t5541-http-push-smart.sh. Update the test
Apache configuration to pass GNUPGHOME environment variable through.
As PassEnv would trigger warnings for an environment variable that
is not set, export it from test-lib.sh set to a harmless value when
GnuPG is not being used in the tests.

Note that the added test is deliberately loose and does not check
the nonce in this step. This is because the stateless RPC mode is
inevitably flaky and a nonce that comes back in the actual push
processing is one issued by a different process; if the two
interactions with the server crossed a second boundary, the nonces
will not match and such a check will fail. A later patch in the
series will work around this shortcoming.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/send-pack.c
remote-curl.c
t/lib-httpd/apache.conf
t/t5541-http-push-smart.sh
t/test-lib.sh
transport-helper.c
index f420b74665bcf1746e94b4cc5eb66ded0a2235ff..ca28d8d2488a3240341e4163f06e1f76ad108b58 100644 (file)
@@ -153,6 +153,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
                                args.verbose = 1;
                                continue;
                        }
+                       if (!strcmp(arg, "--signed")) {
+                               args.push_cert = 1;
+                               continue;
+                       }
                        if (!strcmp(arg, "--progress")) {
                                progress = 1;
                                continue;
index 0fcf2ce5ff20cc7c6f1bdf6257c94cf8fa21b35a..1ea4e95d9a0b782606fc5ce9214ec6bed43e2154 100644 (file)
@@ -25,7 +25,8 @@ struct options {
                update_shallow : 1,
                followtags : 1,
                dry_run : 1,
-               thin : 1;
+               thin : 1,
+               push_cert : 1;
 };
 static struct options options;
 static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@ -106,6 +107,14 @@ static int set_option(const char *name, const char *value)
                else
                        return -1;
                return 0;
+       } else if (!strcmp(name, "pushcert")) {
+               if (!strcmp(value, "true"))
+                       options.push_cert = 1;
+               else if (!strcmp(value, "false"))
+                       options.push_cert = 0;
+               else
+                       return -1;
+               return 0;
        } else {
                return 1 /* unsupported */;
        }
@@ -872,6 +881,8 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
                argv_array_push(&args, "--thin");
        if (options.dry_run)
                argv_array_push(&args, "--dry-run");
+       if (options.push_cert)
+               argv_array_push(&args, "--signed");
        if (options.verbosity == 0)
                argv_array_push(&args, "--quiet");
        else if (options.verbosity > 1)
index b384d7993545365c17f3c8bded756b4e74faf794..7713dd260948c0fca0c0633b6c2cc152f7bc1361 100644 (file)
@@ -68,6 +68,7 @@ LockFile accept.lock
 
 PassEnv GIT_VALGRIND
 PassEnv GIT_VALGRIND_OPTIONS
+PassEnv GNUPGHOME
 
 Alias /dumb/ www/
 Alias /auth/dumb/ www/auth/dumb/
index 73af16f481836d3cbfa8a2f82c260d10a3a43c09..24926a4a42b57ac81a5784d59433a3f8d1fdb108 100755 (executable)
@@ -12,6 +12,7 @@ if test -n "$NO_CURL"; then
 fi
 
 ROOT_PATH="$PWD"
+. "$TEST_DIRECTORY"/lib-gpg.sh
 . "$TEST_DIRECTORY"/lib-httpd.sh
 . "$TEST_DIRECTORY"/lib-terminal.sh
 start_httpd
@@ -323,5 +324,40 @@ test_expect_success 'push into half-auth-complete requires password' '
        test_cmp expect actual
 '
 
+test_expect_success GPG 'push with post-receive to inspect certificate' '
+       (
+               cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
+               mkdir -p hooks &&
+               write_script hooks/post-receive <<-\EOF &&
+               # discard the update list
+               cat >/dev/null
+               # record the push certificate
+               if test -n "${GIT_PUSH_CERT-}"
+               then
+                       git cat-file blob $GIT_PUSH_CERT >../push-cert
+               fi &&
+               cat >../push-cert-status <<E_O_F
+               SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
+               KEY=${GIT_PUSH_CERT_KEY-nokey}
+               STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
+               E_O_F
+               EOF
+
+               git config receive.certnonceseed sekrit
+       ) &&
+       cd "$ROOT_PATH/test_repo_clone" &&
+       test_commit cert-test &&
+       git push --signed "$HTTPD_URL/smart/test_repo.git" &&
+       (
+               cd "$HTTPD_DOCUMENT_ROOT_PATH" &&
+               cat <<-\EOF
+               SIGNER=C O Mitter <committer@example.com>
+               KEY=13B6F51ECDDE430D
+               STATUS=G
+               EOF
+       ) >expect &&
+       test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status"
+'
+
 stop_httpd
 test_done
index b1bc65bfb564ca85d35c42071f825360d1998392..d5939b70f3e9b3e6bd9d3e08d342f90729bc7268 100644 (file)
@@ -813,7 +813,8 @@ rm -fr "$TRASH_DIRECTORY" || {
 }
 
 HOME="$TRASH_DIRECTORY"
-export HOME
+GNUPGHOME="$HOME/gnupg-home-not-used"
+export HOME GNUPGHOME
 
 if test -z "$TEST_NO_CREATE_REPO"
 then
index 3d8fe7d801293a338f13dfd01ba352e84c658813..4b1a26143aad45db5dd68991da692a4cef64e182 100644 (file)
@@ -259,7 +259,8 @@ static const char *unsupported_options[] = {
 static const char *boolean_options[] = {
        TRANS_OPT_THIN,
        TRANS_OPT_KEEP,
-       TRANS_OPT_FOLLOWTAGS
+       TRANS_OPT_FOLLOWTAGS,
+       TRANS_OPT_PUSH_CERT
        };
 
 static int set_helper_option(struct transport *transport,
@@ -835,6 +836,9 @@ static int push_refs_with_push(struct transport *transport,
        if (flags & TRANSPORT_PUSH_DRY_RUN) {
                if (set_helper_option(transport, "dry-run", "true") != 0)
                        die("helper %s does not support dry-run", data->name);
+       } else if (flags & TRANSPORT_PUSH_CERT) {
+               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+                       die("helper %s does not support --signed", data->name);
        }
 
        strbuf_addch(&buf, '\n');
@@ -859,6 +863,9 @@ static int push_refs_with_export(struct transport *transport,
        if (flags & TRANSPORT_PUSH_DRY_RUN) {
                if (set_helper_option(transport, "dry-run", "true") != 0)
                        die("helper %s does not support dry-run", data->name);
+       } else if (flags & TRANSPORT_PUSH_CERT) {
+               if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
+                       die("helper %s does not support dry-run", data->name);
        }
 
        if (flags & TRANSPORT_PUSH_FORCE) {