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

Automatically (Optionally?) Automatically Detect the Appropriate KV Secrets Engine Version #383

Open
jeffwecan opened this issue Jan 22, 2019 · 7 comments
Labels
kv Key/Value (KV) secrets engine

Comments

@jeffwecan
Copy link
Member

Spinning off this issue from: #183

In that issue's comments regarding the implementation of KV v2 hvac support, I made a note along the lines of:

I did not attempt to add any sort of auto-detection of KV versions as of yet though I do like the idea generally. When exploring that option, I wasn't able to work out a way to do that sort of detection in a manner that would do the trick without adding a bit more complexity than I was comfortable with.

In a subsequent comment, @janmasarik provided some additional input:

I was checking out possibilities of auto-detection and came up with simple hack for backwards compatibility. Please let me know what you think about it as I think it may be useful for people migrating from KV 1 to KV 2 on runtime.

  1. Call /secret/config API endpoint on init of the client to dynamically determine if it's KV 1 or KV > 2. It should return either error/invalid response for KV1 and response containing {"data": {"max_versions":0, ...}} for KV2 https://www.vaultproject.io/api/secret/kv/kv-v2.html#read-kv-engine-configuration
  2. Make simple read_secret / list_secrets / ... wrappers for KV2 reading the most recent version of the secret.
  3. Make it configurable in client.kv through some parameter , eg. auto_kv_version_detect=False with warning about this magical behaviour. 😅

What do you think @jeffwecan? Is this that bit more complexity you were talking about?

@jeffwecan jeffwecan added the kv Key/Value (KV) secrets engine label Jan 22, 2019
@jeffwecan
Copy link
Member Author

So my main concern on this topic is centered around the need to perform an additional API request at some point to detect the version. It may not be a large concern, but it is the one sticking point that has prevented me from attempting an implementation.

We could keep a cache of a map of KV mount paths to engine versions, but that has its own complexity concerns. E.g., if we detect that secret/ is a KV v1 mount and cache that information, we'd need to add logic to invalidate that cache if the engine was updated to the latest version.

Overall, those issues seems entirely surmountable, I'm just sharing some background about why it hasn't been implemented quite yet.

For what it is worth, I had cause to look at this sort of KV version detection myself when working through implementing doctest for this codebase. I tentatively ended up with an arrangement along these lines:

class Mount(SystemBackendMixin):
# [...]

    def retrieve_mount_option(self, mount_point, option_name, default_value=None):
        """Retrieve a specific option for the secrets engine mounted under the path specified.

        If no matching option (or no options at all) are discovered, a default value is returned.

        :param mount_point: The path the relevant secrets engine is mounted under.
        :type mount_point: str
        :param option_name: Specifies the name of the option to be retrieved.
        :type option_name: str
        :param default_value: The value returned if no matching option (or no options at all) are discovered.
        :type default_value: Any
        :return: The value for the specified secrets engine's named option.
        :rtype: Any
        """
        secrets_engine_path = '{mount_point}/'.format(mount_point=mount_point)
        secrets_engines_list = self.list_mounted_secrets_engines()
        mount_options = secrets_engines_list[secrets_engine_path].get('options')
        if mount_options is None:
            return default_value

        return mount_options.get(option_name, default_value)
KV Secrets Engine - Version 1
"""""""""""""""""""""""""""""

Current usage:

.. doctest::
   :skipif: client.sys.retrieve_mount_option('secret', 'version', 1) != 1

    >>> client.write('secret/foo', baz='bar', lease='1h')
    >>> read_response = client.read('secret/foo')
    >>> print('Value under path "secret/foo" / key "baz": {val}'.format(
    ...     val=read_response['data']['baz'],
    ... ))
    Value under path "secret/foo" / key "baz": bar
    >>> client.delete('secret/foo')

Which isn't exactly related to the topic being discussed, but seemed relevant enough to include here...

@jeffwecan jeffwecan added this to Needs triage in Bug / Feature Request Triage via automation Jan 22, 2019
@janmasarik
Copy link

I went with https://github.com/hashicorp/vault/blob/master/command/kv_helpers.go#L92 in the end instead of reimplementing the wheel with /secret/config.

@jeffwecan
Copy link
Member Author

Interesting! So for your purposes, are you invoking vault cli directly to determine the KV version or did you use the upstream code as a guide to implement something similar in Python?

@janmasarik
Copy link

Hey, I've just ported the code to python. Invoking the cli directly would be quite silly solution. :-)

With config I had a small issue with permissions, as some tokens may not possibly have access to it. This seems to be the solution on how to detect KV version, as it is by hashicorp itself.

In terms of caching and runtime detection of KV version, I personally gave up and just detected it on initialization. It can definitely be achievable, but I'm not sure if it's worth the struggle as the upgrade itself could take a long time which would in my case mean downtime.

An existing version 1 kv store can be upgraded to a version 2 kv store via the CLI or API, as shown below. This will start an upgrade process to upgrade the existing key/value data to a versioned format. The mount will be inaccessible during this process. This process could take a long time, so plan accordingly.

https://www.vaultproject.io/docs/secrets/kv/kv-v2.html#upgrading-from-version-1

@jeffwecan
Copy link
Member Author

Yeah that is another interesting nuance. That issue actual came up in the form of intermittently failing integration test cases here: #296 -> #361.

@juliantaylor
Copy link

juliantaylor commented Apr 1, 2021

you can use the sys/internal/ui/mounts/$realpath to determine the type of secret engine used for a key-value type path (does not work for e.g. auth/ paths). You automatically have rights to read that path if you have read access to $realpath
https://www.vaultproject.io/api/system/internal-ui-mounts

a kv1 path can either have type generic with no options.version or kv with options.version==1 depending on how old your vault is.

The official vault go client uses the same endpoint to do so, though it is probably considered an non-public api.

@juliantaylor
Copy link

Some code example I have been using for autodetection so it works the same way as vault kv get $path does:

    MOUNT_INFO_PATH = sys/internal/ui/mounts"
    def _get_kv_path(self, path):
        """ adapt path to support kv version 1 and 2 """
        path = path.lstrip("/")
        info_path = "/".join([self.MOUNT_INFO_PATH, path])
        data = self.request(info_path)["data"]
        options = data.get("options")
        mountpath = data["path"].rstrip("/")

        if (data["type"] in ("kv", "generic") and options and 
                int(options.get("version", 1)) == 2): 
            prefix = "/".join([mountpath, "data"]) + "/" 
            kv2 = True
        else:
            prefix = mountpath + "/" 
            kv2 = False

        newpath = re.sub("^" + mountpath + "/", prefix, path)
        return newpath, kv2 

    def _get_secret_data(self, path):
        path, kv2 = self._get_kv_path(path)
        data = self.request(path)["data"]
        if kv2:
            return data["data"]
        else:
            return data

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kv Key/Value (KV) secrets engine
Projects
Development

No branches or pull requests

3 participants