Skip to content

Instantly share code, notes, and snippets.

@djspiewak
Last active August 29, 2015 14:11
Show Gist options
  • Save djspiewak/d8082be3efeb875acd4d to your computer and use it in GitHub Desktop.
Save djspiewak/d8082be3efeb875acd4d to your computer and use it in GitHub Desktop.

Configuring SBT to use 1Password

  1. Install swig-python
  2. Use pip to install 1pass
  3. Install py-levenshtein to avoid annoying warnings
  4. Locate the nearest 1pass script (note: it may be behind you)
  5. Test 1pass on a random password

Create ~/.sbt/0.13/plugins/OnePassword.scala and enter the following contents:

import sbt._

import java.io.ByteArrayInputStream
import java.nio.charset.StandardCharsets

object OnePassword {

  // alright, let's be real here: this is a security leak. sbt-pgp has the same flaw
  private[this] lazy val password =
	SimpleReader.readLine("1Password Master Password: ", Some('*')) getOrElse error("Must provide a password")

  def apply(zone: String, host: String, user: String, entry: String): Credentials = {
	val onePass = Process("/opt/local/Library/Frameworks/Python.framework/Versions/2.7/bin/1pass" :: "--no-prompt" :: entry :: Nil)
	val is = new ByteArrayInputStream(password.getBytes(StandardCharsets.UTF_8))

	val lines = (onePass #< is).lines

	Credentials(
	  zone,
	  host,
	  user,
	  lines.last)         // throw an exception if it doesn't work
  }
}

Pay no mind to the dangerous security leak... If it makes you feel better, sbt-pgp leaks credentials in exactly the same way! Of course, your PGP key password isn't quite as sensitive as your 1Password master password, so maybe you shouldn't feel better after all.

To configure a set of credentials with a password stored in 1Password, follow this pattern (here is my Sonatype credential entry):

credentials +=
  OnePassword(
	"Sonatype Nexus Repository Manager",
	"oss.sonatype.org",
	"djspiewak",
	"Sonatype")

The "Sonatype" final parameter refers to the name of the 1Password entry which contains my Sonatype password. At present, I do not have a way to derive the username from 1Password, though in theory this is possible.

At present, you are prompted for your master password on startup and on the first time you publish. Ideally, it would only be at the latter time, but I honestly don't know how to do that with SBT plugins. Help appreciated!

Troubleshooting

I've noticed that 1pass, and more specifically Python's json library, has trouble parsing older 1Password items that contain forward slashes (/). I'm not 100% certain of why this is, but it happens. You'll see this manifest in the form of an error that looks like this:

1pass: Error: Extra data: line 1 column ...

If you see this error, open up the 1Password item you're trying to load and retype any forward slashes. Chances are, these are in the URL associated with the item. Just edit it, delete the slashes and put them in again. Save the item and you're back in business. Note that it may be possible to replicate this effect by simply editing and then saving the item, but I didn't try that.

Annoyances

Guess what! If you put your credentials in the global settings, they get loaded not just when they're needed by your build, but also when SBT bootstraps itself. This means that you will need to enter your master password when SBT launches (any SBT instance, not just those using your credentials), and then again when you need to publish something. Though, if you publish repeatedly, I believe the password should be cached (hence the security leak). The point being that the completely unnecessary prompting for your password during the bootstrap is really, really annoying and I'd like to bypass it if at all possible, but I don't know how yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment