some more auth stuff and documentation

This commit is contained in:
brent s. 2020-03-29 01:45:12 -04:00
parent e23ef97057
commit feb032b84f
Signed by: bts
GPG Key ID: 8C004C2F93481F6B
5 changed files with 364 additions and 1 deletions

341
docs/README.adoc Normal file
View File

@ -0,0 +1,341 @@
= VaultPass User Manual
Brent Saner <bts@square-r00t.net>
v1.0, 2020-03-28
:doctype: book
:data-uri:
:imagesdir: images
:sectlinks:
:toc: preamble
:toc2: left
:idprefix:
:toclevels: 7
:source-highlighter: highlightjs

== Preface
=== What is Vault?
https://www.vaultproject.io/[Vault by HashiCorp^] is a "secrets manager" - it securely protects various secrets with a
very robust system of authentication and authorization.

It also provides an https://en.wikipedia.org/wiki/X.509[X.509^] https://en.wikipedia.org/wiki/Public_key_infrastructure[PKI^]
system for certificates generation and a token/OTP generator.

=== What is Pass?
https://www.passwordstore.org/[Pass^] ("The standard Unix password manager") is a password manager written entirely in
bash and backed by GPG. It's fairly barebones in terms of technology but does a decent enough job.

=== What is VaultPass?
VaultPass attempts to bridge the gap between the two. It aims to be a drop-in replacement for the pass CLI utility via
subcommands and other operations, but obviously with Vault as a backend instead of GPG-encrypted flatfile hierarchy.

Obviously since the backends are vastly different, total parity is going to be impossible. But I try to get it pretty close.


== Configuration
Unlike Pass, PassVault requires a persistent configuration. At the very **least**, the authentication method needs to be
specified.

The default location for the configuration file is `~/.config/vaultpass.xml`. It's an XML document formatted with the
following structure:

. The https://www.w3.org/TR/xml/#sec-prolog-dtd[XML prolog^], specifying the character encoding of the document and
XML version.footnote:confheader[These aren't **strictly** necessary, but will make cross-parsing and validation MUCH
easier. It's *highly* recommended to use them.]
. The root element (`vaultpass`).
This element contains attributes describing parsing/validation specifics as well, such as the
https://www.w3.org/TR/xml-names/[namespace definitions^] and https://www.w3.org/TR/xmlschema11-1/#xsi_schemaLocation[schema location^].footnote:confheader[]
.. The `server` element.footnote:optelem[This element/attribute/text content is *optional*. See the item's description
for how default values/behaviour are determined.] This element is a container for connection and management of the
Vault server. This consists of:
... A single `uri` element.footnote:optelem[] It should be the same as the **base** URL for your Vault server.
The default (if not specified) is to first check for a **`VAULT_SERVER`** environment variable and, if not found, to use
`http://localhost:8000/`.
... An unseal directive, which can be used to (attempt to) automatically unseal the server if it is sealed.
This isn't required, but can assist in automatic operation.
One of either:footnote:optelem[]
.... `unseal`, the unseal key shard (a Base64 string), or
.... `unsealGpg`, the unseal key shard encrypted with GPG. See the section on <<GPG-Encrypted Elements>>.
... A required authentication directive which specifies how we should authenticate to Vault. It should be comprised of
one of either:
.... `auth` (see <<Auth>> section below), or
.... `authGpg`, an <<Auth>> config snippet encrypted with GPG. See the section on <<GPG-Encrypted Elements>>.

Let's look at an example configuration.

=== Example Configuration

.`~/.config/vaultpass.xml` example:
[source,xml]
----
<?xml version="1.0" encoding="UTF-8" ?>
<vaultpass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://git.square-r00t.net/VaultPass/"
xsi:schemaLocation="https://git.square-r00t.net/VaultPass/ http://schema.xml.r00t2.io/projects/vaultpass.xsd">

<server>
<uri>http://localhost:8000/</uri>
<unseal>YOUR_UNSEAL_SHARD</unseal>
</server>
<auth>
<token/>
</auth>

</vaultpass>
----

In the above, we can see that it would use the vault server at `http://localhost:8000/` using whatever token is either
in the **`VAULT_TOKEN`** environment variable or, if empty, the `~/.vault-token` file. Because an unseal shard was
provided, it will be able to attempt to automatically unseal the Vault (assuming its shard will complete the threshold
needed).

=== Auth
Vault itself supports a https://www.vaultproject.io/docs/auth/[large number of authentication methods^]. However, in
the interest if maintainability, this project has limited support to only the most common authentication methods. More
authentication methods may be added in the future upon request.

NOTE: All of these (except for <<token>>) **require** configuration in Vault first. Configuration of those
authentication methods is out of scope for this document and project. Please ensure that your authentication works as
expected in the https://www.vaultproject.io/downloads/[Vault CLI utility^] or via the
https://www.vaultproject.io/api-docs/auth/[Vault API^] first before submitting a bug report in VaultPass.

==== AppRole
AppRole takes two required children elements:

. `appRole` (the container element)
.. `role`, the AppRole's RoleID, and
.. `secret`, the AppRole's SecretID.

===== Example Snippet
[source,xml]
----
<!-- SNIP -->
<auth>
<appRole>
<role>my-role</role>
<secret>37b74931-c4cd-d49a-9246-ccc62d682a25</secret>
</appRole>
</auth>
<!-- SNIP -->
----

==== LDAP
LDAP takes two required children elements and one optional child element:

. `ldap` (the container element)
.. `username`, the username (as according to the *`userdn`* and *`userattr`* settings
https://www.vaultproject.io/docs/auth/ldap/#binding-parameters[in the configuration^])
.. `password`, the password for the account object.
.. `mountPoint` footnote:optelem[], the https://www.vaultproject.io/api-docs/system/mounts/[mount point^] for the LDAP authentication in
Vault. The default, if not provided, is `ldap`.

===== Example Snippet
[source,xml]
----
<!-- SNIP -->
<auth>
<ldap>
<username>mitchellh</username>
<password>MyPassword1</password>
<mountPoint>ldap</mountPoint>
</ldap>
</auth>
<!-- SNIP -->
----

==== Token
Token auth is the most basic supported authentication in Vault and can be used without any further configuration.

It consists of, at its most basic (and "automagic") configuration, a single element -- but this can be configured more
in-depth/explicitly.

. `token` (the container element)
.. The token itself or content/source of the token.footnote:optelem[]

It has one optional attribute: `source`.footnote:optelem[]. It can be one of the following:

* `env:MY_TOKEN_VAR`, in which environmental token **`MY_TOKEN_VAR`** will be sourced.
* A filesystem path, in which the file is assumed to contain the token (and ONLY the token).

To determine the behaviour of how this behaves, please refer to the below table.

.Determining `token` behaviour
[cols="^1,5,10"]
|===
|No. |If... |Then...

| 1 |self-enclosed, no `source` |The **`VAULT_TOKEN`** environment variable is checked. If not defined, the file
`~/.vault-token` will be checked. If that file doesn't exist, a `RuntimeError` will be raised.
| 2 |self-enclosed, `source` given| The `source` is assumed to be the *only* source and no automatic detection will occur.
| 3 |token contained in tags, no `source`| The specified token will be used and no automatic detection will occur.
| 4 |token contained in tags, `source` given |Same as **3**; `source` is ignored.
|===

===== Example Snippet
[source,xml]
----
<!-- SNIP -->
<auth>
<!-- "Automagic" (#1).
First $VAULT_TOKEN environment variable is checked, then ~/.vault-token is checked. -->
<token/>

<!-- Source is considered the only place to fetch token from (#2). -->
<!-- This would check the environment variable $SOMEVAR -->
<!-- <token source="env:SOMEVAR"/> -->
<!-- This would use the contents of ~/.vault-token.alt -->
<!-- <token source="~/.vault-token.alt"/> -->

<!-- Token explicitly given is the only one used. -->
<!-- <token>s.Lp4ix1CKBtJOfA46Ks4b4cs6</token> -->

<!-- Token explicitly given is the only one used; source attribute is ignored. -->
<!-- <token source="env:THIS_IS_IGNORED">s.Lp4ix1CKBtJOfA46Ks4b4cs6</token> -->
</auth>
<!-- SNIP -->
----

==== User/Password
Vault's https://www.vaultproject.io/docs/auth/userpass/[userpass authentication method^] must be
https://www.vaultproject.io/docs/auth/userpass/#configuration[configured^] beforehand, but it's a relatively simple
configuration.

VaultPass user/password authentication takes two required children elements and one optional element.

. `userpass` (the container element)
.. `username`, the username of the account.
.. `password`, the password for the account.
.. `mountPoint` footnote:optelem[], the https://www.vaultproject.io/api-docs/system/mounts/[mount point^] for the auth.
If not specified, the default is `userpass`.

===== Example Snippet
[source,xml]
----
<!-- SNIP -->
<auth>
<userpass>
<username>mitchellh</username>
<password>foo</password>
<mountPoint>userpass</mountPoint>
</userpass>
</auth>
<!-- SNIP -->
----

=== GPG-Encrypted Elements
Understandably, in order to have a persistent configuration, that means storing on disk. That also means that they need
to be able to be accessed with no or minimal user interruption. Pass used GPG natively, so it didn't have an issue with
this; since https://www.gnupg.org/documentation/manuals/gnupg/Invoking-GPG_002dAGENT.html[gpg-agent^] is typically
spawned on first use of a https://www.gnupg.org/gph/en/manual/r1616.html[GPG homedir^] (usually `~/.gnupg/` by default)
and keeps an authenticated session open for 10 minutes
(https://superuser.com/questions/624343/keep-gnupg-credentials-cached-for-entire-user-session[by default^]).

To get around needing to store plaintext credentials on-disk in any form, VaultPass has `unsealGpg` and `authGpg`
elements. These elements are of the same composition (described <<gpg_elements, below>>) and allow you to use GPG to
encrypt that sensitive information.

Note that while this does increase security, it breaks compatibility with other XML parsers - they won't be able to
decrypt and parse the encrypted snippet unless explicitly coded to do so.

==== `*Gpg` elements
`*Gpg` elements (`authGpg`, `unsealGpg`) have the same structure:

. `unsealGpg`/`authGpg`, the container element.
.. The path to the encrypted file as the contained text.

It has some optional attributes as well:

.`*Gpg` element attributes
|===
|Attribute |Content

|`keyFPR` footnote:optelem[] | The GPG key to use to decrypt the file. It accepts multiple key ID formats, but it's *highly* recommended to
use the full 40-character (without spaces) key fingerprint. If not specified, VaultPass will use the first private key
it finds in the keyring.
|`gpgHome` footnote:optelem[] | The GPG home directory. If not specified, VaultPass will first check the **`GNUPGHOME`** environment
variable. If that's empty, we'll default to `~/.gnupg/`.
|===

The contents of the encrypted file should match the **unencrypted** XML content it's replacing.

CAUTION: Note that if you use namespaces in your `vaultpass.xml` config file, you **MUST** use matching declarations in
your encrypted file.

Let's look at an example of GPG-encrypted elements.

==== GPG-Encrypted Elements Example

.`~/.config/vaultpass.xml` snippet:
[source,xml]
----
<?xml version="1.0" encoding="UTF-8" ?>
<vaultpass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://git.square-r00t.net/VaultPass/"
xsi:schemaLocation="https://git.square-r00t.net/VaultPass/ http://schema.xml.r00t2.io/projects/vaultpass.xsd">

<server>
<uri>http://localhost:8000/</uri>
<unsealGpg keyFPR="D34DB33FD34DB33FD34DB33FD34DB33FD34DB33F"
gpgHome="~/.gnupg">~/.private/vaultpass/unseal.asc</unsealGpg>
</server>
<authGpg keyFPR="D34DB33FD34DB33FD34DB33FD34DB33FD34DB33F"
gpgHome="~/.gnupg">~/.private/vaultpass/auth.gpg</unsealGpg>
</vaultpass>
----

As shown, it supports both <<ascii_armored>> and <<binary>> encryption formats.

==== ASCII-Armored
===== Encrypted
.`~/.private/vaultpass/unseal.asc` contents:
[source]
----
-----BEGIN PGP MESSAGE-----

hQIMA7QuYg9nGdZdAQ//eHvEZ7vpLvygM2ofIiT2uW7cWYQaYm/09li7s0+0ZqTu
hNki7oIQ1Ip+k6ds45eEXPG6hXwZ7+mtIDG8VcYpo0PdwpvcJ9qqAgvnFAynvjgH
pRkeIw4VUfGxxhs8oZMvdrXuYtwzaXIhn0UuZv+cIS1Jj6IfG0xSpRvd+M0MW+Wk
IWSIyUcY6fkP7MFEiId7sQwm6htHXJDqiVAmwn4lqk2CnIhtsTd5HUyRzGg5gZs+
sFAssa7QjoBKJMkTDVH4EIC4GcgNtTB/rg7XBoX1k36CHZAwB/boZ5arMYswwkYp
VFv9At13vkkRMf23bb7siq7U0Vbvs0PGsFJS/1ivS1IyzFGFZGHaTz7ndk2q2iyY
tMjMe+z+i2VAGvtfdE7H4K4TrqrM9OZ81vyJkEjRBrkSfR9sWOgv5yBFDvoeVkZl
k1gRXLkrF/7eZn8vD17oOew/zr+um7s/rTtLp5GEknOsKzb1NOMBHP44dXdxNreT
HdRlNDLgOp2KffXgNSm/A026tMSA0nf0kpJmR1yLjucKPoy6wVrTMh+sLNubgxmZ
BCz64myu8dfWtHQfPSis1kjrs15mfQoOu9Cl9st8gTs50sKWTa+dGdajZEcz8rcX
OMBLwiTQodP/0uRHf8YofIFk86QXbYALd4WsC/KvDQBiaz8HRcfkccDQCHQvdLrS
wEkBuhCZj1OqUnTXg0qggMD0Hp2pO0CqD4uZ3RHvIt49W+7oUr22Y4VarRNeP06x
JhYC3Sr0RXv/Vi21DMiUUUAXYeYKP82HpP0zSZhCcwVZZje1dXwq85SH04u9pT+n
f2JqgATxmAaepQZCANxAluknfSluuCBi0hmhagYY2IsgKmJcSsksm0AWfGyzgoeV
ZypDlE3MuERVLJSDBjZtfnScy3CeTWWj5vw7Nfm5XEqOuIIbZaTV/qb6i6y4rc6k
Yx5xYKHeuXJGbrQdVJemcXyDIV5tDw5RtLpO57EwL+uEYgSbN9rO/N2B83QjB7D5
lCmbJtQcjxG/eJ/SrB2oS47YdEKRy+cH0Xx+
=scGv
-----END PGP MESSAGE-----
----

===== Decrypted
[source,xml]
----
<unseal xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://git.square-r00t.net/VaultPass/"
xsi:schemaLocation="https://git.square-r00t.net/VaultPass/ http://schema.xml.r00t2.io/projects/vaultpass.xsd">1fs1tV46ebb6awF6edtuzsoEawZlBARFp5rSaED+EJI=</unseal>
----

==== Binary
===== Encrypted
.`~/.private/vaultpass/auth.gpg` contents:
[source]
----
<BINARY DATA>
----


===== Decrypted
[source,xml]
----
<auth xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://git.square-r00t.net/VaultPass/"
xsi:schemaLocation="https://git.square-r00t.net/VaultPass/ http://schema.xml.r00t2.io/projects/vaultpass.xsd">

<token>s.Lp4ix1CKBtJOfA46Ks4b4cs6</token>

</auth>
----

3
docs/gendocs.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash

asciidoctor README.adoc -o ./README.html

View File

@ -29,7 +29,7 @@ class PassMan(object):
def getClient(self): def getClient(self):
# This may need to be re-tooled in the future. # This may need to be re-tooled in the future.
auth_xml = self.cfg.xml.find('auth') auth_xml = self.cfg.xml.find('auth')
authmethod_xml = auth_xml.getchildren()[0]\ authmethod_xml = auth_xml.getchildren()[0]
for a in dir(auth): for a in dir(auth):
if a.startswith('_'): if a.startswith('_'):
continue continue

View File

@ -128,3 +128,6 @@ class Token(_AuthBase):
self.token = self._getEnv(e) self.token = self._getEnv(e)
else: else:
self.token = self._getFile(a) self.token = self._getFile(a)
self.client.token = self.token
self.authCheck()
return(None)

16
vaultpass/gpgauth.py Normal file
View File

@ -0,0 +1,16 @@
import os
import logging
##
import gpg


# Special shoutout to Jthan for ruining my life.


_logger = logging.getLogger()


class GPGAuth(object):
def __init__(self, gpgauth_xml):
pass