Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow exporting all environment variables by default for secrets env #312

Merged
merged 1 commit into from
Oct 21, 2024

Conversation

aryanjassal
Copy link
Contributor

@aryanjassal aryanjassal commented Oct 18, 2024

Description

This is a child PR of #305 which only implements the required changes to allow the secrets env to use the vault name only without needing to specify the secrets.

Currently, we do this: polykey secrets env vault:. to export all secrets from a vault.
After this, we can do polykey secrets env vault to automatically export all the secrets in the vault.

This PR will also allow using dots in vault names. More robust checks will be implemented as a part of the parent PR.

Issues Fixed

Tasks

  • 1. Allow dots to be included in the vault name
  • 2. Allow secrets env to export all secrets if no secret path is specified

Final checklist

  • Domain specific tests
  • Full tests
  • Updated inline-comment documentation
  • Lint fixed
  • Squash and rebased
  • Sanity check the final build

@aryanjassal
Copy link
Contributor Author

We have run into a roadblock with the implementation of secrets env. We are running the following command in a test, which runs secrets env and prints out the environment variables to stdout.

command = [
  'secrets',
  'env',
  '-np',
  dataDir,
  '--env-format',
  'unix',
  '--',
  `${vaultName}:dir1=SECRET_NEW`,
  'node',
  '-e',
  'console.log(JSON.stringify(process.env))',
];

The parser for this command first tries to parse each argument as a secret path. The previous implementation would disallow node to be a valid secret path, so it would start tracking all the args after that as the actual command. However, now that empty secret path does refer to a valid secret path (the root of the vault) instead of throwing a parsing error, the current parser just parses each argument as a valid vault name.

This doesn't allow for the additional commands to be accumulated, as we can no longer differentiate between the command and the vault paths. Until a solution for handling this is discussed, this PR is essentially blocked.

(Brian's commentary on this)

Its a common problem in parsing byte streams. If the data can be any byte then how do you split up the stream, any delimiter could actually be a value or vice versa. There's not a lot of options here that look nice.

  1. There's length delimiting where you specify the length of each message at the beginning. Not great for args.
  2. There's a delimter that you exclude from the values altogether.
  3. Or you could use a delimiter and escape it in the values.

None of these are great but the original method was basically option 2.

We might be able to add a separate option which might take variadic parameters for entering the command, or maybe takes a string as the command. This needs some discussion and inputs before we can move ahead with this PR.

@CMCDragonkai @tegefaulkes

Copy link

linear bot commented Oct 18, 2024

@aryanjassal
Copy link
Contributor Author

We can use an option with variadic arguments as such:

class CommandEnv extends CommandPolykey {
  constructor(...args: ConstructorParameters<typeof CommandPolykey>) {
    super(...args);
    this.name('env');
    this.argument(
      '<args...>',
      'arguments formatted as [envPaths...]',
      binParsers.parseEnvArgs,
    );
    this.option(
      '--command <args...>
      'command formatted as [cmd][cmdArgs...]',
    );
    this.action(async (args: Array<string>, options) => {
      console.log(options.command); // Example: ['node', '-e', 'console.log(JSON.stringify(process.env))']
...

So, the new command invocation can look like this:

[aryanj@matrix-34xx:~]$ polykey secrets env vault --command node -e console.log("hello world")

Should this be implemented in this PR, or is there another approach to this?

@CMCDragonkai @tegefaulkes

@CMCDragonkai
Copy link
Member

We can use an option with variadic arguments as such:

class CommandEnv extends CommandPolykey {
  constructor(...args: ConstructorParameters<typeof CommandPolykey>) {
    super(...args);
    this.name('env');
    this.argument(
      '<args...>',
      'arguments formatted as [envPaths...]',
      binParsers.parseEnvArgs,
    );
    this.option(
      '--command <args...>
      'command formatted as [cmd][cmdArgs...]',
    );
    this.action(async (args: Array<string>, options) => {
      console.log(options.command); // Example: ['node', '-e', 'console.log(JSON.stringify(process.env))']
...

So, the new command invocation can look like this:

[aryanj@matrix-34xx:~]$ polykey secrets env vault --command node -e console.log("hello world")

Should this be implemented in this PR, or is there another approach to this?

@CMCDragonkai @tegefaulkes

I want it to work like env in the default case so --command shouldn't be used.

@aryanjassal
Copy link
Contributor Author

command = [
  'secrets',
  'env',
  '-np',
  dataDir,
  '--env-format',
  'unix',
  '--',
  `${vaultName}:dir1=SECRET_NEW`,
  'node',
  '-e',
  'console.log(JSON.stringify(process.env))',
];

The -- can go after the vault name, and then that would interpret the following arguments as the command, and everything before that would be interpreted as the vault names.

@aryanjassal
Copy link
Contributor Author

aryanjassal commented Oct 21, 2024

const { Command } = require('commander');
const program = new Command();

program
  .command('secrets env [vaults...]')
  .description('Set environment with vaults and execute a command')
  .allowUnknownOption(true) // Allows capturing anything after `--` as unknown options
  .action((vaults, options) => {
    // Find the `--` separator in the arguments to capture everything after it.
    const indexOfDoubleDash = process.argv.indexOf('--');
    let command = [];

    // If `--` is found, get all arguments after it.
    if (indexOfDoubleDash !== -1) {
      command = process.argv.slice(indexOfDoubleDash + 1);
    }

    console.log('vaults:', vaults);
    console.log('command:', command);
  });

program.parse(process.argv);

This is what ChatGPT gave me as a possible implementation. I will use this for reference when implementing this.

It also gave me this regex to match vault names:

^[\p{Print}&&[^/\p{Cc}]]+$

This will match all printable UTF-8 characters (from the {Print} group) while disallowing control characters (from the {Cc}group) and additionally the/`.

As we are not currently dealing with local file systems, this can be kept simple while retaining possibility of updates.

@aryanjassal
Copy link
Contributor Author

aryanjassal commented Oct 21, 2024

const { Command } = require('commander');
const program = new Command();

program
  .command('secrets env [vaults...]')
  .description('Set environment with vaults and execute a command')
  .allowUnknownOption(true) // Allows capturing anything after `--` as unknown options
  .action((vaults, options) => {
    // Find the `--` separator in the arguments to capture everything after it.
    const indexOfDoubleDash = process.argv.indexOf('--');
    let command = [];

    // If `--` is found, get all arguments after it.
    if (indexOfDoubleDash !== -1) {
      command = process.argv.slice(indexOfDoubleDash + 1);
    }

    console.log('vaults:', vaults);
    console.log('command:', command);
  });

program.parse(process.argv);

This is what ChatGPT gave me as a possible implementation. I will use this for reference when implementing this.

I have realised that commander doesn't provide the location of -- or differentiate according to that. If we need to identify the location of --, then we need to do so with process.argv, which I believe completely undermines the purpose of commander.

And if we are already requiring the users to put the command after --, might as well make it --run -- or something.

The env command also relies on the syntax for the environment variables to be <KEY>=<VALUE>, and if the syntax doesn't follow this, then it would be classified as a command. However, if we want to just export all secrets in a vault, and the vault name can resemble a command and vice-versa, then implicitly detecting the beginning of the commands isn't viable anymore.

@aryanjassal
Copy link
Contributor Author

aryanjassal commented Oct 21, 2024

Nevermind, I got it working.

Commander by default parses options positionlessly. Meaning, polykey secrets list -np 'path' would have been parsed the same as polykey secrets -np 'path' list. However, we can set Command.passThroughOptions(), which will force commander to parse everything after starting a variadic argument as part of it. Then, it will also parse -- as a part of the variadic argument. This can then be detected by the parser, and it can then signify the switch to parsing the rest as commands.

For example, polykey secrets env vault1 vault2 -- command arg1 arg2 will have the following passed into its parser: ['vault1', 'vault2', '--', 'command', 'arg1', 'arg2'].

Some more testing on this is needed, but from what I can tell, it seems to work pretty well.

@aryanjassal
Copy link
Contributor Author

I have noticed that for all the tests, we are checking for the exact expected error code (like 64 for usage). For my secrets commands, I have just been testing if the exit code is not zero. Does this need to change? If so, I can track this change in some issue somewhere.

@aryanjassal aryanjassal marked this pull request as ready for review October 21, 2024 04:28
feat: fixed secrets env to allow vault names without secret path

fix: actually allow vault to export all secrets by default

fix: build

chore: changed vaultName:. to vaultName in tests
@aryanjassal
Copy link
Contributor Author

The required change for allowing dots in vault names and defaulting the path to vault root has been implemented. This change isn't meant to be a complete and bulletproof change, but meant to be enough to allow env migration work to be done.

This PR can now be merged. The other features are being tracked in #305

@aryanjassal aryanjassal merged commit 65a02e6 into staging Oct 21, 2024
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

Specify only the vault path and infer the secret path to point to root
3 participants