slipset / deps-deploy

deploy your stuff
Eclipse Public License 1.0
148 stars 31 forks source link

Provide a secure-by-default mechanism for reading credentials #2

Closed SevereOverfl0w closed 3 years ago

SevereOverfl0w commented 5 years ago

A few options immediately spring to mind in implementing this:

  1. Read a GPG encrypted file. I would strongly consider not even offering a way to load them unencrypted from this file.
  2. Read ~/.m2/settings.xml, which supports encrypted parameters. This might be useful in enterprise settings, but I'm not sure.
  3. Provide a GPG-less encrypted file option? I would punt this down the road, but I can understand that not everyone uses GPG.
  4. I've seen mail clients that allow you to run any shell command in order to provide the password. This is particularly neat if you use a password manager, as you can provide a command like pass show clojars.org and the user runs on whatever encryption they please.

Option 4 allows you to implement 1 very easily, while also handling a large number of people who would want 3.

There's a great opportunity here to redefine the security level for deploying jars, which has historically been quite weak.

slipset commented 5 years ago

Since I'm not very well versed in this area:

1) Here you're considering a file like ./clojars_creds.gpg which contains eg username:password and is encrypted by gpg. The flow here would be that deps-deploy would ask the user for the gpg-password on deploy, decrypt the contents of ./clojars_creds.gpg and pass the decrypted username and password to aether?

slipset commented 5 years ago

https://clojureverse.org/t/please-how-do-you-configure-gpg-and-clojars/606

SevereOverfl0w commented 5 years ago

The flow here would be that deps-deploy would ask the user for the gpg-password on deploy, decrypt the contents of ./clojars_creds.gpg and pass the decrypted username and password to aether

Yep, pretty much on the mark of what I'm thinking. It would essentially run the command gpg2 -d ~/.clojars_creds.edn.gpg and pass it on to aether.

ieugen commented 3 years ago

Regarding passwords I would like to see support for settings.xml . It's supported by deps.edn for downloading dependencies.

I believe the flow is described here https://maven.apache.org/plugins/maven-deploy-plugin/usage.html and implemented by maven-deploy-plugin .

In this case, you can specify a server definition in your settings.xml to provide authentication information for both of these repositories at once. Your server section might look like this:

[...]
<server>
<id>internal.repo</id>
<username>maven</username>
<password>foobar</password>
</server>
[...]

Please see the article about Password Encryption for instructions on how to avoid clear text passwords in the settings.xml.

Once you've configured your repository deployment information correctly deploying your project's artifact will only require invocation of the deploy phase of the build:

IMO we need a function that will handle settings.xml loading and settings-security.xml loading.

I believe this library should provide the plumbing https://maven.apache.org/ref/3.8.2/maven-settings/apidocs/index.html
https://maven.apache.org/ref/3.8.2/maven-settings/summary.html org.apache.maven/maven-settings {:mvn/version "3.8.2"}

Class SettingsXpp3Reader .

ieugen commented 3 years ago

I managed to make this work (decode server password from maven settings). I would like to see it upstream. Are you interested @slipset ? I would be happy to contribute a PR.

Sample code:

(Adapted from https://github.com/jelmerk/maven-settings-decoder/blob/master/src/main/java/org/github/jelmerk/maven/settings/Decoder.java )

deps.edn

{:paths ["target/data"]
 :deps {}
 :aliases {:build {:deps {io.github.clojure/tools.build {:git/tag "v0.1.9" :git/sha "6736c83"}
                          org.apache.maven/maven-settings {:mvn/version "3.8.2"}
                          org.apache.maven/maven-settings-builder {:mvn/version "3.8.2"}
                          org.clojure/java.data {:mvn/version "1.0.86"}
                          org.clojure/tools.deps.alpha {:mvn/version "0.12.1036"}
                          org.sonatype.plexus/plexus-sec-dispatcher {:mvn/version "1.4"}
                          slipset/deps-deploy {:mvn/version "0.1.5"}}
                   :ns-default build}}}

build.clj

(ns build
  (:require [clojure.java.io :as io]
            [clojure.string :as str]
            [clojure.tools.build.api :as b]
            [clojure.tools.deps.alpha :as tools]
            [deps-deploy.deps-deploy :as dd])
  (:import [java.io FileInputStream]
           [org.apache.maven.building FileSource]
           [org.apache.maven.settings.building
            DefaultSettingsBuilderFactory
            DefaultSettingsBuildingRequest]
           [org.apache.maven.settings.crypto
            DefaultSettingsDecrypter
            DefaultSettingsDecryptionRequest]
           [org.apache.maven.settings.io.xpp3 SettingsXpp3Reader]
           [org.sonatype.plexus.components.cipher
            DefaultPlexusCipher]
           [org.sonatype.plexus.components.sec.dispatcher
            DefaultSecDispatcher
            SecUtil]))

(defn ^org.apache.maven.settings.Server read-maven-settings! 
  "Read settings from settings.xml file."
  [settings-path]
  (let [sr (SettingsXpp3Reader.)]
    (with-open [rdr (io/reader settings-path  )]
      (.read sr rdr))))

(defn ^String decode-password 
  [^String encoded-password
   ^String key]
  (-> (DefaultPlexusCipher.)
      (.decryptDecorated encoded-password key)))

(defn ^String decode-master-password
  [^String encoded-master-password]
  (decode-password encoded-master-password DefaultSecDispatcher/SYSTEM_PROPERTY_SEC_LOCATION))

(defn ^org.sonatype.plexus.components.sec.dispatcher.model.SettingsSecurity read-settings-security
  [^java.io.File file]
  (SecUtil/read (.getAbsolutePath file) true))

(defn ^org.apache.maven.settings.Settings read-settings 
  [^java.io.File file]
  (-> (SettingsXpp3Reader.)
      (.read (FileInputStream. file))))

(comment 

  (let [settings (read-settings (io/as-file "/home/ieugen/.m2/settings.xml"))
        settings-security (read-settings-security (io/as-file "/home/ieugen/.m2/settings-security.xml"))
        encoded-master-pw (.getMaster settings-security)
        plain-master-pw (decode-master-password encoded-master-pw)
        servers (.getServers settings)]
    (doseq [s servers]
      (let [encoded-pw (.getPassword s)
            plain-pw (decode-password encoded-pw plain-master-pw)]
        (println (str/join (repeat 20 "-")))
        (println "Credentials for server" (.getId s) "are")
        (println "Username :" (.getUsername s))
        (println "Password :" plain-pw))))

0)
ieugen commented 3 years ago

I think the issue to solve now is how to integrate this with the deploy API.

ieugen commented 3 years ago

I made a PR that adds a maven-settings namespace with functions to read settings.xml and decode passwords from that. People can store encrypted passwords in maven settings.xml and use those functions to read and decode the passwords. Then we can use the credentials to deploy to a repository. Matching should be done by repo (server) id.

slipset commented 3 years ago

Excellent, and thank you. I'll have a look at this during the week.