signed push: teach smart-HTTP to pass "git push --signed" around

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>

Junio C Hamano committed Sep 15, 2014 at 14:59 UTC 0ea47f9d3307bdb1cd9364acd3e4a463b244bba2
6 files changed +63 -3
builtin/send-pack.c
+4
@@ -153,6 +153,10 @@ int cmd_send_pack(int argc, const char **argv, const char *prefix)
153 args.verbose = 1;
154 continue;
155 }
156 + if (!strcmp(arg, "--signed")) {
157 + args.push_cert = 1;
158 + continue;
159 + }
160 if (!strcmp(arg, "--progress")) {
161 progress = 1;
162 continue;
remote-curl.c
+12 -1
@@ -25,7 +25,8 @@ struct options {
25 update_shallow : 1,
26 followtags : 1,
27 dry_run : 1,
28 - thin : 1;
28 + thin : 1,
29 + push_cert : 1;
30 };
31 static struct options options;
32 static struct string_list cas_options = STRING_LIST_INIT_DUP;
@@ -106,6 +107,14 @@ static int set_option(const char *name, const char *value)
107 else
108 return -1;
109 return 0;
110 + } else if (!strcmp(name, "pushcert")) {
111 + if (!strcmp(value, "true"))
112 + options.push_cert = 1;
113 + else if (!strcmp(value, "false"))
114 + options.push_cert = 0;
115 + else
116 + return -1;
117 + return 0;
118 } else {
119 return 1 /* unsupported */;
120 }
@@ -872,6 +881,8 @@ static int push_git(struct discovery *heads, int nr_spec, char **specs)
881 argv_array_push(&args, "--thin");
882 if (options.dry_run)
883 argv_array_push(&args, "--dry-run");
884 + if (options.push_cert)
885 + argv_array_push(&args, "--signed");
886 if (options.verbosity == 0)
887 argv_array_push(&args, "--quiet");
888 else if (options.verbosity > 1)
t/lib-httpd/apache.conf
+1
@@ -68,6 +68,7 @@ LockFile accept.lock
68
69 PassEnv GIT_VALGRIND
70 PassEnv GIT_VALGRIND_OPTIONS
71 +PassEnv GNUPGHOME
72
73 Alias /dumb/ www/
74 Alias /auth/dumb/ www/auth/dumb/
t/t5541-http-push-smart.sh
+36
@@ -12,6 +12,7 @@ if test -n "$NO_CURL"; then
12 fi
13
14 ROOT_PATH="$PWD"
15 +. "$TEST_DIRECTORY"/lib-gpg.sh
16 . "$TEST_DIRECTORY"/lib-httpd.sh
17 . "$TEST_DIRECTORY"/lib-terminal.sh
18 start_httpd
@@ -323,5 +324,40 @@ test_expect_success 'push into half-auth-complete requires password' '
324 test_cmp expect actual
325 '
326
327 +test_expect_success GPG 'push with post-receive to inspect certificate' '
328 + (
329 + cd "$HTTPD_DOCUMENT_ROOT_PATH"/test_repo.git &&
330 + mkdir -p hooks &&
331 + write_script hooks/post-receive <<-\EOF &&
332 + # discard the update list
333 + cat >/dev/null
334 + # record the push certificate
335 + if test -n "${GIT_PUSH_CERT-}"
336 + then
337 + git cat-file blob $GIT_PUSH_CERT >../push-cert
338 + fi &&
339 + cat >../push-cert-status <<E_O_F
340 + SIGNER=${GIT_PUSH_CERT_SIGNER-nobody}
341 + KEY=${GIT_PUSH_CERT_KEY-nokey}
342 + STATUS=${GIT_PUSH_CERT_STATUS-nostatus}
343 + E_O_F
344 + EOF
345 +
346 + git config receive.certnonceseed sekrit
347 + ) &&
348 + cd "$ROOT_PATH/test_repo_clone" &&
349 + test_commit cert-test &&
350 + git push --signed "$HTTPD_URL/smart/test_repo.git" &&
351 + (
352 + cd "$HTTPD_DOCUMENT_ROOT_PATH" &&
353 + cat <<-\EOF
354 + SIGNER=C O Mitter <committer@example.com>
355 + KEY=13B6F51ECDDE430D
356 + STATUS=G
357 + EOF
358 + ) >expect &&
359 + test_cmp expect "$HTTPD_DOCUMENT_ROOT_PATH/push-cert-status"
360 +'
361 +
362 stop_httpd
363 test_done
t/test-lib.sh
+2 -1
@@ -813,7 +813,8 @@ rm -fr "$TRASH_DIRECTORY" || {
813 }
814
815 HOME="$TRASH_DIRECTORY"
816 -export HOME
816 +GNUPGHOME="$HOME/gnupg-home-not-used"
817 +export HOME GNUPGHOME
818
819 if test -z "$TEST_NO_CREATE_REPO"
820 then
transport-helper.c
+8 -1
@@ -259,7 +259,8 @@ static const char *unsupported_options[] = {
259 static const char *boolean_options[] = {
260 TRANS_OPT_THIN,
261 TRANS_OPT_KEEP,
262 - TRANS_OPT_FOLLOWTAGS
262 + TRANS_OPT_FOLLOWTAGS,
263 + TRANS_OPT_PUSH_CERT
264 };
265
266 static int set_helper_option(struct transport *transport,
@@ -835,6 +836,9 @@ static int push_refs_with_push(struct transport *transport,
836 if (flags & TRANSPORT_PUSH_DRY_RUN) {
837 if (set_helper_option(transport, "dry-run", "true") != 0)
838 die("helper %s does not support dry-run", data->name);
839 + } else if (flags & TRANSPORT_PUSH_CERT) {
840 + if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
841 + die("helper %s does not support --signed", data->name);
842 }
843
844 strbuf_addch(&buf, '\n');
@@ -859,6 +863,9 @@ static int push_refs_with_export(struct transport *transport,
863 if (flags & TRANSPORT_PUSH_DRY_RUN) {
864 if (set_helper_option(transport, "dry-run", "true") != 0)
865 die("helper %s does not support dry-run", data->name);
866 + } else if (flags & TRANSPORT_PUSH_CERT) {
867 + if (set_helper_option(transport, TRANS_OPT_PUSH_CERT, "true") != 0)
868 + die("helper %s does not support dry-run", data->name);
869 }
870
871 if (flags & TRANSPORT_PUSH_FORCE) {