Go 1.8 plugin model

Go 1.8 adds a plugin mode that I find very interesting. It would allow a more traditional “load all plugins in this folder” mode vs the current style of building them into caddy.

On startup, caddy could simply load all plugin files in a folder, probably denoted by a flag, or maybe some convention in the .caddy folder. It wouldn’t need to do anything else at all, since the init functions would take care of registering things.

It is only for linux and darwin at the moment, but perhaps would be useful as an option nonetheless?

3 Likes

Hey Craig!

I’m definitely keeping my eye on its development. I am glad you posted about it so I won’t forget, I would be happy to see the community discussing things like this.

I wonder what the performance impacts of a plugin are.

And, of course, if Caddy did offer this, I’m not sure it would be widely available until plugins worked on Windows too.

But this is definitely a possibility if the feature matures a bit.

@matt

I will buy you at least two of your beverage of choice (should we ever meet) if you implement plugins literally the second this feature matures, with a --pluginsdir flag, or something to that effect.

If so, we could tackle automated caddy building and packaging (at least for debian) in one fell swoop!

1 Like

It also has to cross-compile still. The import "C" in the example doesn’t look too encouraging. :-/

Anyway, yes, plugins would be nice to have assuming the way it works meets certain criteria. But the existing static binaries/builds will probably be around to stay since the infrastructure is already going into place. :slight_smile:

It definitely opens up a lot of possibilities. Would be much easier to have an app store type thing for plugins (as a plugin itself!), or runtime plugin activation, or whatever you want.

I don’t think cgo is required, but I’ll see. Planning to stay up late tonight to try it.

1 Like

It looks like the plugin package does use cgo, so I’m not sure what implications that has for cross-platform compilation.

Making Caddy (and as much functionality as possible) available on Windows is pretty important to me and the goals of the project, but already it doesn’t support signals on Windows, so I mean, I dunno, maybe that’s not a showstopper.

What I don’t want to see happen is that Caddy has a bunch of “linux+darwin only” plugins that don’t work on Windows.

See this for info on plugin overhead:

Been trying to work on this, but keep hitting issues with the linker / plugin system. plugin: link error building with plugin package on darwin · Issue #18104 · golang/go · GitHub is the latest.

Super excited for a few different reasons:

  • could run caddy with a -plugins="git,cors,hugo" type flag to download the requested plugins and load them at first startup.
  • could have an appstore plugin that pulls from a registry and presents a simple web ui. Can interactively pull plugins, install and restart caddy.

The caddy build server could just always serve the caddy core binaries, and then maintain a registry of plugins and serve the compiled plugin files on demand.

One issue I really don’t like though is that plugins require a main package, while caddy plugins are all libraries. It kinda stinks to have to make a main package that really only contains import _ "theRealPluginPackage", but maybe the build server could abstract that little piece of monotony.

1 Like

That’s awesome Craig. I’ll follow up with some effort going on over here.

Cool, I’m not opposed to this, it will just need some very careful consideration if supported officially. I still maintain that static binaries have significant advantages that Caddy as a project holds dear.

@yroc92 has started building an official front-end web UI for Caddy. It is being designed to be very extensible. Before going too far with that idea, wait until we have something to show and see how you can integrate with it. We’re working through lots of details, but if you have questions about it, I’m sure he’d be happy to answer it.

I don’t think we could rely on the 1.8 plugin model for everyone, though, since at least for now it looks like it won’t work on Windows, which is actually the plurality platform of Caddy downloads (surprising, right?). And besides, the new build server is already written for dynamically doing static builds on-demand. And the developer portal (website backend) is maintaining a registry of plugins. Or at least it will soon, when it and the new website are done. :slight_smile:

That is kind of annoying, and it’s how plugins already work but instead of the plugin’s main package, it’s in caddy’s main package.

Yeah, we’d definitely still need to make full custom binaries with all the plugins, for windows and for people who want that.

From the benchmarks at GitHub - wendigo/go-bind-plugin: go-bind-plugin generates API for exported plugin symbols (-buildmode=plugin) - go1.8+ only (http://golang.org/pkg/plugin), I’m hopeful that there is no real additional call overhead after loading the plugin one time. I can benchmark once I get it working.

Seems like the new build server might have close to everything we need. An additional endpoint to build a single plugin binary may be the only addition needed.

One main use case I think this could help would be for packaged distributions. You don’t need to make a seperate deb package with different variations of plugins installed, or a proliferation of various combinations of docker images. Just have the infrastructure in place to let the user extend it after installation.

Regarding the main thing, yeah, it stinks. I thought maybe I could work around it in the go tool, but I don’t think so anymore.

The build server could easily generate the needed main stub though if it is distributing plugins. If/when the go team fixes my last real blocker I’ll submit a POC branch.

1 Like

You mean plugins with main functions? I’m not sure what value a plugin has as its own binary?

None whatsoever. But a main package is required to use -buildmode=plugin. So its really a matter of making literally:

package main

import _ "github.com/abiosoft/caddy-git"

func main(){}

to make it compilable as a plugin. Either plugin authors would need to add a useless package to make it compatible with plugins, or the buildserver could magically create that when making plugins form libraries.

1 Like

A plugin binary as opposed to a standalone binary.

I see, on both things. Thanks for clarifying!

Hi, thx for linking to my project.

As I also contribute small pieces of code to Caddy I’m interested in making Caddy “pluginable”, but as for now I see following obstacles:

  • plugins are limited to macOS and Linux only (no support for windows, bsd and other platforms)
  • I’m not really sure if macOS support will not be dropped in 1.8 (it’s broken as of current tip)
  • plugin image contains whole go runtime so whole caddy distribution will be much bigger than it is now (every plugin will add overhead of around 2 MB)
  • there are literally no security features - plugins cannot be signed so some kind of security features must be built

It does not make sense to “plugify” core Caddy’s plugins. It makes sense to distribute external plugins as go plugins in a long term (will simplify building and distribution of caddy).

For now plugins just gave me a lot of headache: see plugin: cannot load a plugin from a test where the plugin includes the tested package · Issue #17928 · golang/go · GitHub and plugin: link error building with plugin package on darwin · Issue #18104 · golang/go · GitHub.

1 Like

It looks like they are gonna drop osx support for plugins before go 1.8 is finalized. That means they would be linux only at least for another 6 months.

Not sure how to address security. Perhaps each plugin main package could have a signed string constant inserted by the official build server at build time. You could (optionally) check that against a preshared public key when loading plugins. But how do you trust that your caddy build with the key is valid? Chickens and eggs.

I think I saw an issue about potentially not including the runtime in plugins. That would definitely cut down size. Now I just have all kinds of questions about what happens when a plugin and the host app are built against different versions of dependencies. If they have to be super lock stepped, it may not be worth the trouble.

Anyway, I still love the idea, but it seems plugins are currently one of the roughest edges in the go universe.

Actually the last problem is solved - each plugin’s dependencies when loaded are checked against host dependencies versions (it happens here: go/plugin.go at master · golang/go · GitHub).

I agree - plugins are rough and we will have to wait another 6-12 months to get it working on other architectures.

I have a pretty good POC that works on linux only (maybe ok if the target is easier plugin support in prebuilt packages).

The more I think about the security of it, though, the more scared I am. There is no way to determine if a file is a valid caddy plugin before you load it (and let it run code). It could also just be a binary that someone put in the folder in hopes that caddy would run it.

Caddy can create a plugins folder inside .caddy with extremely limited access rights, so hopefully only caddy can put stuff in there (unless you go out of your way to change it). That would provide a minimal level of protection. People have a habit of running caddy as root though, so I don’t really want to hand wave this.

Perhaps a manifest file with something like:

[{
"file": git.plugin,
"sha": "12345",
"sig": "45678",
}]

Where the signature is a cryptographic signature of the sha using the build server’s public key (or a local caddydev or something with a separate key pair)

I no longer think plugins are gonna be a viable model. Mostly because it is too hard to ensure that every plugin is built against the exact same versions of packages as the host app is. Even with extensive vendoring, you wouldn’t be able to update caddy without re-fetching plugins, or vise versa.

The problem I am trying to solve is really to have a single package you can install with apt or yum or whatever, and be able to customize it with whatever plugins you want.

How about this:

A (possibly built-in) plugin customizer.

localhost:8585 {
   customizer
}

Hosts a very simple app that gets the plugin list from the build server and lets you select what you want (and shows what you currently have). On making a selection, it will download a new custom binary, copy it over the currently running one, and issue a restart.

Assuming all of that would work (which may be a tad tricky), it would let you customize your plugins at “runtime”.

Thoughts?

1 Like