Skip to content

Commit

Permalink
Revert "Merge pull request #2279 from gonewest818/unattended-signatures"
Browse files Browse the repository at this point in the history
This reverts commit c3de05b, reversing
changes made to dd019de.

# Conflicts:
#	leiningen-core/src/leiningen/core/user.clj
  • Loading branch information
technomancy committed Dec 12, 2018
1 parent 689b62f commit 1c277ed
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 144 deletions.
29 changes: 0 additions & 29 deletions doc/GPG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,12 @@
- [How Leiningen uses GPG](#how-leiningen-uses-gpg)
- [Signing a file](#signing-a-file)
- [Overriding the gpg defaults](#overriding-the-gpg-defaults)
- [Setting the gpg passphrase for unattended deploys](#setting-the-gpg-passphrase-for-unattended-deploys)
- [Troubleshooting](#troubleshooting)
- [Debian based distributions](#debian-based-distributions-1)
- [gpg: can't query passphrase in batch mode](#gpg-cant-query-passphrase-in-batch-mode)
- [Mac OSX](#mac-osx)
- [Unable to get GPG installed via Homebrew and OSX Keychain to work](#unable-to-get-gpg-installed-via-homebrew-and-osx-keychain-to-work)
- [GPG doesn't ask for a passphrase](#gpg-doesnt-ask-for-a-passphrase)
- [gpg: decryption failed: secret key not available](#gpg-decryption-failed-secret-key-not-available)
- [GPG prompts for passphrase but does not work with Leiningen](#gpg-prompts-for-passphrase-but-does-not-work-with-leiningen)

<!-- END doctoc generated TOC please keep comment here to allow auto update -->
Expand Down Expand Up @@ -252,33 +250,6 @@ repository specification in your project definition:
["snapshots" "https://blueant.com/archiva/internal/snapshots"]]
...)

### Setting the gpg passphrase for unattended deploys

It's also possible to provide the passphrase required to unlock your
keyring. This is meant only for unattended deploys, for example in a
continuous integration system like Travis CI or CircleCI or Jenkins.

Passphrase can be configured in the environment:

(defproject ham-biscuit "0.1.0"
...
:signing {:gpg-key "bob@bobsons.net"
:gpg-passphrase :env/gpgpass} ;; looks up GPGPASS from env
...)

In your CI service your gpg keyring will need to be encrypted and
injected into the build, and the passphrase likewise encrypted such that
the environment variable is visible only to the build.

For testing purposes the pasphrase can also be set as a string literal
but this is strongly discouraged in any production usage.

(defproject ham-biscuit "0.1.0"
...
:signing {:gpg-key "bob@bobsons.net"
:gpg-passphrase "my-passphrase-in-the-clear"}
...)

## Troubleshooting

### Debian based distributions
Expand Down
89 changes: 26 additions & 63 deletions leiningen-core/src/leiningen/core/user.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"Functions exposing user-level configuration."
(:require [clojure.java.io :as io]
[clojure.string :as str]
[clojure.java.shell :as shell]
[leiningen.core.utils :as utils])
(:import (com.hypirion.io Pipe)
(org.apache.commons.io.output TeeOutputStream)
Expand Down Expand Up @@ -103,30 +104,20 @@
[env]
(into-array String (map (fn [[k v]] (str (name k) "=" v)) env)))

(defn gpg-with-passphrase
"Shells out to (gpg-program) with the given arguments and, if
passphrase is not nil, sends the passphrase on stdin for unattended
operations such as signing artifacts for deployment. When a passphrase
is provided the caller must include the following args
[\"--passphrase-fd\" \"0\" \"--pinentry-mode\" \"loopback\"]
along with whatever other args are needed for the gpg command."
[passphrase & args]
(defn gpg
"Shells out to (gpg-program) with the given arguments"
[& args]
(try
(let [proc-env (as-env-strings (get-english-env))
proc-args (into-array String (concat [(gpg-program)] args))
proc (.exec (Runtime/getRuntime) proc-args proc-env)]
(.addShutdownHook (Runtime/getRuntime)
(Thread. (fn [] (.destroy proc))))
(with-open [out (.getInputStream proc)
err (.getErrorStream proc)
err-output (ByteArrayOutputStream.)]
(if passphrase
(with-open [in (.getOutputStream proc)]
(io/copy passphrase in)))
(let [pump-err (doto (Pipe. err
(TeeOutputStream. System/err
err-output))
.start)]
(let [pump-err (doto (Pipe. (.getErrorStream proc)
(TeeOutputStream. System/err err-output))
.start)]
(.join pump-err)
(let [exit-code (.waitFor proc)]
{:exit exit-code
Expand All @@ -135,26 +126,11 @@
(catch java.io.IOException e
{:exit 1 :out "" :err (.getMessage e)})))

(defn gpg
"Shells out to (gpg-program) with the given arguments"
[& args]
(apply gpg-with-passphrase nil args))

(defn gpg-available?
"Verifies (gpg-program) exists"
[]
(zero? (:exit (gpg "--version"))))

(defn gpg-version
"parse and return the version of gpg available"
[]
(let [pattern #"gpg\s+\(GnuPG\)\s+(\d+)\.(\d+)\.(\d+)"]
(if-let [[_ major minor patch]
(re-find pattern (:out (gpg "--version")))]
{:major (Integer/parseInt major)
:minor (Integer/parseInt minor)
:patch (Integer/parseInt patch)})))

(defn credentials-fn
"Decrypt map from credentials.clj.gpg in Leiningen home if present."
([] (let [cred-file (io/file (leiningen-home) "credentials.clj.gpg")]
Expand All @@ -180,41 +156,28 @@
(re-find re? (:url settings)))]
cred))))

(defn resolve-env-keyword
"Resolve usage of :env and :env/foo in project.clj"
[k v]
(cond (= :env v)
(getenv (str "LEIN_"
(-> (name k)
(str/upper-case)
(str/replace "-" "_"))))

(and (keyword? v) (= "env" (namespace v)))
(getenv (str/upper-case (name v)))

:else nil))

(defn- resolve-gpg-keyword
"Resolve usage of :gpg in project.clj"
[source-settings k v]
(if (= :gpg v)
(get (match-credentials source-settings (credentials)) k)))

(defn- resolve-kv [source-settings k v]
(or (resolve-env-keyword k v)
(resolve-gpg-keyword source-settings k v)
(if (coll? v) ;; collection of places to look
(->> (map (partial resolve-kv source-settings k) v)
(filter string?)
first))
v))

(defn- resolve-credential
"Resolve key-value pair from result into a credential, updating result."
[source-settings result [k v]]
(if (#{:username :password :passphrase :private-key-file} k)
(assoc result k (resolve-kv source-settings k v))
(assoc result k v)))
(letfn [(resolve [v]
(cond (= :env v)
(getenv (str "LEIN_" (str/upper-case (name k))))

(and (keyword? v) (= "env" (namespace v)))
(getenv (str/upper-case (name v)))

(= :gpg v)
(get (match-credentials source-settings (credentials)) k)

(coll? v) ;; collection of places to look
(->> (map resolve v)
(remove nil?)
first)

:else v))]
(if (#{:username :password :passphrase :private-key-file} k)
(assoc result k (resolve v))
(assoc result k v))))

(defn resolve-credentials
"Applies credentials from the environment or ~/.lein/credentials.clj.gpg
Expand Down
31 changes: 5 additions & 26 deletions src/leiningen/deploy.clj
Original file line number Diff line number Diff line change
Expand Up @@ -74,37 +74,16 @@
(add-auth-interactively))))

(defn signing-args
"Produce GPG arguments for signing a file, taking the version of gpg
into account as necessary."
"Produce GPG arguments for signing a file."
[file opts]
(let [key-args (concat
(if-let [key (:gpg-key opts)]
["--default-key" key])
(if (:gpg-passphrase opts)
(let [{:keys [major minor patch]} (user/gpg-version)
version (+ major (/ minor 10.))]
(if (> version 2.0)
; gpg 2.1 and newer
["--passphrase-fd" "0"
"--pinentry-mode" "loopback"]
; gpg 2.0 and older
["--passphrase-fd" "0" "--batch"]))))]
`["--yes" "-ab" ~@key-args "--" ~file]))

(defn signing-passphrase
"Produce GPG passphrase if specified in project"
[opts]
(if-let [pp (:gpg-passphrase opts)]
(or (user/resolve-env-keyword :gpg-passphrase pp)
pp)))
(let [key-spec (if-let [key (:gpg-key opts)]
["--default-key" key])]
`["--yes" "-ab" ~@key-spec "--" ~file]))

(defn sign
"Create a detached signature and return the signature file name."
[file opts]
(let [pass (signing-passphrase opts)
args (signing-args file opts)
_ (main/info "Signing: gpg " args)
{:keys [err exit]} (apply user/gpg-with-passphrase (cons pass args))]
(let [{:keys [err exit]} (apply user/gpg (signing-args file opts))]
(when-not (zero? exit)
(main/abort "Could not sign"
(str file "\n" err (if err "\n")
Expand Down
27 changes: 1 addition & 26 deletions test/leiningen/test/deploy.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
(:use [clojure.test]
[clojure.java.io :only [file]]
[leiningen.deploy]
[leiningen.core.user :as user]
[leiningen.test.helper :only [delete-file-recursively
tmp-dir sample-project
sample-deploy-project]]))
Expand Down Expand Up @@ -67,31 +66,7 @@
(is (= (signing-args "foo.jar" nil)
["--yes" "-ab" "--" "foo.jar"]))
(is (= (signing-args "foo.jar" {:gpg-key "123456"})
["--yes" "-ab" "--default-key" "123456" "--" "foo.jar"]))
(with-redefs [user/gpg-version (fn [] {:major 2 :minor 1 :patch 0})]
(is (= (signing-args "foo.jar" {:gpg-key "123456" :gpg-passphrase "abc"})
["--yes" "-ab" "--default-key" "123456"
"--passphrase-fd" "0" "--pinentry-mode" "loopback"
"--" "foo.jar"]))
(is (= (signing-args "foo.jar" {:gpg-passphrase "abc"})
["--yes" "-ab"
"--passphrase-fd" "0" "--pinentry-mode" "loopback"
"--" "foo.jar"])))
(with-redefs [user/gpg-version (fn [] {:major 1 :minor 4 :patch 0})]
(is (= (signing-args "foo.jar" {:gpg-key "123456" :gpg-passphrase "abc"})
["--yes" "-ab" "--default-key" "123456"
"--passphrase-fd" "0" "--batch"
"--" "foo.jar"]))
(is (= (signing-args "foo.jar" {:gpg-passphrase "abc"})
["--yes" "-ab"
"--passphrase-fd" "0" "--batch"
"--" "foo.jar"])))
(is (= (signing-passphrase {}) nil))
(is (= (signing-passphrase {:gpg-passphrase "abc"}) "abc"))
(with-redefs [user/getenv (fn [v] (if (= v "LEIN_GPG_PASSPHRASE") "abc" nil))]
(is (= (signing-passphrase {:gpg-passphrase :env}) "abc")))
(with-redefs [user/getenv (fn [v] (if (= v "MYPASS") "abc" nil))]
(is (= (signing-passphrase {:gpg-passphrase :env/mypass}) "abc"))))
["--yes" "-ab" "--default-key" "123456" "--" "foo.jar"])))
(testing "Key selection"
(is (= (:gpg-key (signing-opts {:signing {:gpg-key "key-project"}}
["repo" {:signing {:gpg-key "key-repo"}}]))
Expand Down

0 comments on commit 1c277ed

Please sign in to comment.