Last active
August 29, 2015 14:11
Revisions
-
djspiewak revised this gist
Dec 16, 2014 . 1 changed file with 62 additions and 21 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -6,6 +6,12 @@ 4. Locate the nearest `1pass` script (note: it may be behind you) 5. Test `1pass` on a random password Create `~/.sbt/0.13/plugins/build.sbt` with the following contents: ```sbt sbtPlugin := true ``` Create `~/.sbt/0.13/plugins/OnePassword.scala` and enter the following contents: ```scala @@ -14,43 +20,76 @@ import sbt._ import java.io.ByteArrayInputStream import java.nio.charset.StandardCharsets object OnePassword extends AutoPlugin { object autoImport { val isSbtBuild = settingKey[Boolean]("Detects SBT meta projects") } // 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)(enabled: Boolean): Seq[Credentials] = { if (enabled) { 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 Seq(Credentials( zone, host, user, lines.last)) } else { Seq() } } } ``` 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. I have an [issue](https://github.com/sbt/sbt-pgp/issues/68) open to get this problem addressed in SBT itself, but it probably won't happen before 0.14. Create `~/.sbt/0.13/onepass.sbt` with the following contents: ```sbt import OnePassword.autoImport._ isSbtBuild := Keys.sbtPlugin.?.value.getOrElse(false) && (Keys.baseDirectory in ThisProject).value.getName == "project" ``` You can name this file anything you like. It just has to exist somewhere in the `0.13` directory. To configure a set of credentials with a password stored in 1Password, follow this pattern (here is my Sonatype credential entry): ```sbt import OnePassword.autoImport._ credentials ++= OnePassword( "Sonatype Nexus Repository Manager", "oss.sonatype.org", "djspiewak", "Sonatype")(!isSbtBuild.value) ``` 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. The `isSbtBuild` setting is introduced by this plugin detects whether or not the enclosing SBT context represents one of the many meta-builds which bootstrap SBT itself. Using this setting ensures that we *only* prompt for the master password when it is required for a task within your specific build (e.g. `update` or `publish`). If you find yourself needing this credential set for meta-build resolution (e.g. if you have a custom plugin that is stored in a Nexus protected by your credentials), you will need to replace the `credentials` line with something like the following: ```sbt credentials ++= OnePassword( "Sonatype Nexus Repository Manager", "oss.sonatype.org", "djspiewak", "Sonatype")(true) ``` This will force you to enter your master password quite a bit though, so I don't recommend going this route unless you absolutely have to. ## Troubleshooting @@ -62,12 +101,14 @@ I've noticed that `1pass`, and more specifically Python's `json` library, has tr 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. ## Ways Around the Leak One stupidly obvious way around the leak is to just obfuscate the password at rest in memory. This sounds like a funky idea, but it actually does a passable job of addressing the primary threat vector here, which is someone crafting a build that reflectively peaks into the `OnePassword.password` field and exfiltrates the contents. This exfiltration is only useful if the data is relatively easy to reverse. A simple obfuscation of the password with a *hard-coded* key (i.e. *not* stored in a variable) should force some more significant labor involving classloaders or plain old file IO and parsing. Not impossible, but harder. If each person who uses this plugin hard-codes their own custom key, that would put a hefty speed-bump in the way of any wide scale attack. Another way around the leak is to deactivate the caching altogether. This is relatively easy to do (just change the `lazy val` to a `def`), but unfortunately it does require you to reenter your master password once for every single credentials entry backed by 1Password, and that gets annoying to say the least. Pick your poison. As I mentioned, I've opened an issue on `sbt-pgp` that proposes the addition of a `SecureCache` to SBT, which would neatly resolve this issue and make it very difficult (but far from impossible!) for a malicious build or plugin to extract your master password. Hopefully this solution gets implemented with all speed. ## Special Thanks Josh Suereth is awesome. That is all. -
djspiewak revised this gist
Dec 15, 2014 . 1 changed file with 6 additions and 0 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -65,3 +65,9 @@ If you see this error, open up the 1Password item you're trying to load and *ret ## 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. ## Ways Around the Leak One stupidly obvious way around the leak is to just obfuscate the password at rest in memory. This sounds like a funky idea, but it actually does a passable job of addressing the primary threat vector here, which is someone crafting a build that reflectively peaks into the `OnePassword.password` field and exfiltrates the contents. This exfiltration is only useful if the data is relatively easy to reverse. A simple obfuscation of the password with a *hard-coded* key (i.e. *not* stored in a variable) should force some more significant labor involving classloaders or plain old file IO and parsing. Not impossible, but harder. If each person who uses this plugin hard-codes their own custom key, that would put a hefty speed-bump in the way of any wide scale attack. Another way around the leak is to deactivate the caching altogether. This is relatively easy to do (just change the `lazy val` to a `def`), but unfortunately it does require you to reenter your master password once for every single credentials entry backed by 1Password, and that gets annoying to say the least. Pick your poison. -
djspiewak revised this gist
Dec 15, 2014 . 1 changed file with 16 additions and 2 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -35,7 +35,7 @@ object OnePassword { } ``` 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): @@ -50,4 +50,18 @@ credentials += 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. -
djspiewak created this gist
Dec 15, 2014 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,53 @@ # 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: ```scala 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 should 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): ```sbt 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!