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

Add plugin version to GRPC interface #17088

Merged
merged 21 commits into from
Sep 15, 2022
Merged

Conversation

swenson
Copy link
Contributor

@swenson swenson commented Sep 9, 2022

Added a version interface in the sdk/logical so that it can be shared between all plugin types, and then wired it up to RunningVersion in the mounts, auth list, and database systems.

I've tested that this works with auth, database, and secrets plugin types, with the following logic to populate RunningVersion:

  • If a plugin has a Version() method implemented, then that is used
  • If not, and the plugin is built into the Vault binary, then the go.mod version is used
  • Otherwise, the it will be the empty string.

My apologies for the length of this PR.

Base automatically changed from run-versioned-plugins to main September 9, 2022 17:14
@swenson swenson force-pushed the vault-8143/plugin-version-grpc branch from d590e4b to 0886a5d Compare September 9, 2022 17:17
@tvoran
Copy link
Member

tvoran commented Sep 9, 2022

It looks like a lot of the failed tests are using the PassthroughBackend, so I wonder if that needs another type assertion in request_handling.go and the factory wrapped in request_handling_test.go:

diff --git a/vault/request_handling.go b/vault/request_handling.go
index b37085c83c..95243bb38a 100644
--- a/vault/request_handling.go
+++ b/vault/request_handling.go
@@ -1076,7 +1076,7 @@ func (c *Core) handleRequest(ctx context.Context, req *logical.Request) (retResp
                                return nil, auth, retErr
                        }
 
-                       if ptbe, ok := matchingBackend.(*PassthroughBackend); ok {
+                       if ptbe, ok := matchingBackend.(builtinVersionBackend).backend.(*PassthroughBackend); ok {
                                if !ptbe.GeneratesLeases() {
                                        registerLease = false
                                        resp.Secret.Renewable = false
diff --git a/vault/request_handling_test.go b/vault/request_handling_test.go
index cfdea525f7..b79601039b 100644
--- a/vault/request_handling_test.go
+++ b/vault/request_handling_test.go
@@ -17,7 +17,7 @@ import (
 func TestRequestHandling_Wrapping(t *testing.T) {
        core, _, root := TestCoreUnsealed(t)
 
-       core.logicalBackends["kv"] = PassthroughBackendFactory
+       core.logicalBackends["kv"] = wrapFactoryAddBuiltinVersion(PassthroughBackendFactory)
 
        meUUID, _ := uuid.GenerateUUID()
        err := core.mount(namespace.RootContext(nil), &MountEntry{

sdk/logical/logical.go Outdated Show resolved Hide resolved
sdk/plugin/pb/backend.proto Outdated Show resolved Hide resolved
sdk/plugin/pb/backend.proto Outdated Show resolved Hide resolved
sdk/plugin/grpc_backend_client.go Outdated Show resolved Hide resolved
@swenson
Copy link
Contributor Author

swenson commented Sep 12, 2022

Thanks @tvoran for the tip about the passthroughbackend -- that was exactly the problem.

@swenson swenson requested a review from a team September 12, 2022 22:55
@swenson swenson marked this pull request as draft September 12, 2022 22:56
@swenson
Copy link
Contributor Author

swenson commented Sep 12, 2022

I converted this into a draft since I have a bit more work to do before it's quite ready. I went ahead and removed the plugin wrapper and used the build information to get the plugin version, as Tom suggested, which simplifies things a little.

Christopher Swenson added 7 commits September 13, 2022 10:44
Unlike the POC, I went ahead and added it into the main interface. So
far in my testing, this has been safe to do.

I also took a slightly different approach for how to include the
`v1.12.0+builtin` version in the builtin plugins: I added a new wrapper
struct that sets the version to the "builtin" version if it isn't
already set, and I wrap all of the builtin factories with that when we
create the map of factories. This way we don't have to update every
builtin plugin to add the version logic. If we do want to version those
plugins separately, though, we will be able to do so.

There are still some TODOs before merging this, I feel, but this is
getting large so I wanted to check in:

* [ ] More manual testing with existing plugins and updated (with
  version) ones
* [ ] Update identity/cubbyhole/sys/etc. special backends to support
  version method.
@swenson swenson force-pushed the vault-8143/plugin-version-grpc branch from 34ec9ba to 4626aa8 Compare September 13, 2022 17:47
@swenson swenson force-pushed the vault-8143/plugin-version-grpc branch from 6e99f04 to fdf2894 Compare September 13, 2022 20:04
Christopher Swenson added 9 commits September 13, 2022 13:11
We use a placeholder backend (previously a framework.Backend) before a
GRPC plugin is lazy-loaded. This makes us later think the plugin is a
builtin plugin.

So we added a `placeholderBackend` type that overrides the
`IsExternal()` method so that later we know that the plugin is external,
and don't give it a default builtin version.
@swenson swenson marked this pull request as ready for review September 14, 2022 20:38
@swenson
Copy link
Contributor Author

swenson commented Sep 14, 2022

@tvoran @tomhjp this is ready for review, I believe. I think I addressed all of your comments, and I did a lot of testing.

@@ -874,42 +869,3 @@ func isPluginType(s string) bool {
_, err := consts.ParsePluginType(s)
return err == nil
}

func (c *PluginCatalog) getBuiltinVersion(pluginType consts.PluginType, pluginName string) string {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this function to more central place so it could be used more easily. It didn't really need access to the pluginCatalog to work.

@tomhjp tomhjp added this to the 1.12 milestone Sep 15, 2022
Copy link
Contributor

@tomhjp tomhjp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! It might be that my comment about validating the self-reported version during registration would be best left for another PR if that's non-trivial

// don't override a manually specified plugin version
// but if it is empty, then add in the self-reported version for informational purposes
if config.PluginVersion == "" {
config.PluginVersion = dbw.Version().Version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could cause issues the next time the config is updated if it's different to the version we did the initial lookup with, as next time we'll lookup the reported version and maybe get a different entry from the catalog. Could we call this method as we're registering the plugin, and either a) Use the reported version in the catalog if none was provided or b) Error if the reported version is different from the one provided? Then we shouldn't need to make any updates here as the catalog and reported versions will always match up.

It might be interesting to query the version from the plugin and validate that it's the version we think it should be or error out, though I can't think of any reason that would fail if we validate during plugin registration.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is definitely possible to add a check between PluginVersion and the self-reported version on registration.

I primarily added this because, if no version is specified on registration, it is otherwise impossible to tell what version of the plugin is running, and I wanted to expose this somewhere.

It doesn't even really make sense to have this information in the config at all, but there's no mount or auth table for database configs.

b.Backend = &framework.Backend{
PathsSpecial: paths,
BackendType: btype,
b.Backend = &placeholderBackend{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

placeholderBackend.IsExternal() always returns true, but the function comment for Backend implies the backend could be builtin. Not sure if I'm just misreading though?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, you're right, the placeholder is used even for builtin plugins. I'll see if I can just forward the IsExternal method.

changelog/17088.txt Show resolved Hide resolved
}

// Versioned is an optional RPC service implemented by plugins.
service Versioned {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WDYT about PluginVersion to differentiate from Vault version and protocol version?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

// VersionReply is the reply for the Version method.
message VersionReply {
string plugin_name = 1;
string version = 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I originally wanted to use version everywhere, so started sprinkling that around. But then we've found a couple of places where we need to use plugin_version to avoid ambiguity. I'm wondering if we should standardise on plugin_version everywhere for consistency, and for an abundance of clarity?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that makes sense. I'll rename it.

return logical.EmptyVersion
}
}
b.Logger().Warn("Unknown error getting plugin version", "err", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something would have to go seriously wrong for this to happen, which makes me a bit nervous to only log it server-side and continue. I see Type does something similar above, but perhaps it's worth adding an error return value to our new interface signature before we first ship it so that we have an easier time if we have need for it at a later date. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to mimic Type's relative safety -- worst case scenario, the Version will just return "". I can think of few reasons why this might fail, like if the plugin dies or is running too slowly.

(Type won't even log a warning if an error occurs. :))

I'm open to adding an error return though; I was just trying to keep it as simple as possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah it's definitely nice to have that simplicity. I was thinking a little of possible future changes to the plugin architecture, e.g. what if we wanted to support remote plugins where transport errors might become less unusual. It's perfectly possible the error will never be a particularly useful part of the API though, so I'm not 100% about it.

sdk/plugin/grpc_backend_server.go Show resolved Hide resolved
vault/auth.go Show resolved Hide resolved
* Change changelog to improvement
* Make IsExternal configurable for plugin placeholder
* Start changing version to pluginVersion everywhere
@swenson swenson modified the milestones: 1.12, 1.12.0-rc1 Sep 15, 2022
@swenson swenson force-pushed the vault-8143/plugin-version-grpc branch from 7e7581a to 0e6d9e6 Compare September 15, 2022 20:36
@swenson
Copy link
Contributor Author

swenson commented Sep 15, 2022

@tomhjp I think I addressed your concerns. PTAL.

Copy link
Contributor

@tomhjp tomhjp left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Member

@tvoran tvoran left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a couple naive questions.

builtin/plugin/backend.go Show resolved Hide resolved
sdk/framework/backend.go Show resolved Hide resolved
@swenson
Copy link
Contributor Author

swenson commented Sep 15, 2022

Thanks!

@swenson swenson merged commit 70278c2 into main Sep 15, 2022
@swenson swenson deleted the vault-8143/plugin-version-grpc branch September 15, 2022 23:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants