Skip to content
This repository was archived by the owner on Jul 23, 2024. It is now read-only.

Add CLI tool for Approzium #196

Open
tyrannosaurus-becks opened this issue Aug 11, 2020 · 1 comment
Open

Add CLI tool for Approzium #196

tyrannosaurus-becks opened this issue Aug 11, 2020 · 1 comment
Assignees
Labels
enhancement New feature or request

Comments

@tyrannosaurus-becks
Copy link
Contributor

tyrannosaurus-becks commented Aug 11, 2020

This issue is to add a CLI tool for Approzium. The tool will initially be intended to improve UX around adding passwords to the secrets manager, but we expect that it will be extended to include more features in the future.

Background

Currently, to add a password to a secrets manager, a user must directly add the secrets to HashiCorp Vault or AWS Secrets Manager (ASM) using a very rigid format. If they make any slight typo, Approzium will not work. For Vault, they must place secrets at this path in a KV version 1 store:

approzium/<DATABASE_HOST:DATABASE_PORT>

For ASM, the path is slightly different:

approzium/<DATABASE_HOST@DATABASE_PORT>

For both, the body must be exactly:

{
    "dbuser1": {
        "password": "asdfghjkl",
        "iam_arns": [
            "arn:aws:iam::accountid:role/rolename1",
            "arn:aws:iam::accountid:role/rolename2"
        ]
    }
}

Proposal

I propose that we add a CLI tool to improve UX around seeding passwords for Approzium. Generally, we would allow parameters to be passed in as command-line flags so they can be easily automated in a script.

  • Every required parameter, if not passed by flag, will be asked interactively instead.
  • We will reuse Approzium code everywhere possible, both for configuration, and for using the client that calls the secrets manager.

Methods: Config Management

Writing a config

approzium config write tls \
  -disable-tls=false \
  -tls-cert-path=/path/to/approzium.pem \
  -tls-key-path=/path/to/approzium.key

Success!

The anatomy of the config write command is approzium config [method] [section]. The section is an optional field that allows the user to only configure one portion of the total configuration. When written, configuration will be stored at ~/.approzium/config.

Reading a config

approzium config read tls

  disable_tls: false
  tls_cert_path: "/path/to/approzium.pem"
  tls_key_path: "/path/to/approzium.key"

Configuration is located at ~/.approzium/config.
approzium config read

secrets:
  secrets_manager: "vault"
  vault_addr: "https://somewhere:8200"
  vault_token_path: "/path/to/tokensink.txt"
tls:
  disable_tls: false
  tls_cert_path: "/path/to/approzium.pem"
  tls_key_path: "/path/to/approzium.key"

Configuration is located at ~/.approzium/config.

Methods: Password Management

Writing a password

approzium passwords write \
  -host=postgres.somewhere.com \
  -port=5432 \
  -user=my-db-user \
  -grant-access-to=somearn1,somearn2,somearn3 \
  -password=my-secret-password

Success!

For this command, all flags except password would be required. If password were not provided, we would allow them to provide it interactively (just like psql does). The interactive option for the password would be available as a security measure in case the user didn't want the password to appear in their bash shell's scroll back history.

This command would also be used for updating a password. There would be no field merging or resolution - the new value would simply clobber the old one for that user.

Reading a password record

approzium passwords read \
  -host=postgres.somewhere.com \
  -port=5432 \
  -user=my-db-user \
  -grant-access-to=somearn1,somearn2,somearn3

For security, this command would not return the password. Each flag field would be optional and considered a search filter. All flag fields would be considered optional, and if none were provided, this would behave exactly like the list command. Under the hood, we would essentially list all databases and users in the database, and then iterate through them and filter upon the given fields. We would return records in a human-readable format:

host: postgres.somewhere.com
port: 5432
user: my-db-user
grant-access-to: somearn1,somearn2,somearn3

host: postgres.somewhere-else.com
port: 5432
user: my-db-user
grant-access-to: somearn1,somearn2,somearn3

Or:

No matches found.

Deleting a password

approzium passwords delete \
  -host=postgres.somewhere.com \
  -port=5432 \
  -user=my-db-user \
  -grant-access-to=somearn1,somearn2,somearn3 \
  --disable-dryrun

Successfully deleted matches, if they existed!

This would behave exactly like reading a password. By default, --disable-dryrun would be false and the response would be:

This command would delete the following records:

host: postgres.somewhere.com
port: 5432
user: my-db-user
grant-access-to: somearn1,somearn2,somearn3

host: postgres.somewhere-else.com
port: 5432
user: my-db-user
grant-access-to: somearn1,somearn2,somearn3

Run with --disable-dryrun to delete.

Or:

No matches found.

Listing passwords

approzium passwords list

This would be a convenience method for viewing all stored passwords, and would behave exactly like the read command does when no filters are provided.

UX

The returned text would be formatted with color and potentially emoji.

Out of Scope

  • I would like it if we eventually also renamed the authenticator binary to the approzium binary so users only need to download one binary. But, I think that should be in a separate iteration so we can do it in a thoughtful and hopefully backwards-compatible way.
  • Formatting responses in a machine-readable format would be nice to add in the future, but not in the same iteration to keep it simple.
  • It would be cool if we did additional verification that hosts, usernames, and passwords given ACTUALLY WORK, but that will be out of scope for this iteration because we probably should make that optional, and this will already be a good chunk of code.
@tyrannosaurus-becks tyrannosaurus-becks added the enhancement New feature or request label Aug 11, 2020
@tyrannosaurus-becks
Copy link
Contributor Author

tyrannosaurus-becks commented Aug 11, 2020

An implementation note - here's a function that can be used to interactively ask a question and get a response from a user in Go:

func ask(reader *bufio.Reader, question string) (response string, err error) {
	fmt.Println(question)
	return reader.ReadString('\n')
}

In that example, the reader is: reader := bufio.NewReader(os.Stdin).

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant