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

feat(plugins): Download remote plugins #358

Merged
merged 7 commits into from
Aug 19, 2019

Conversation

link108
Copy link
Member

@link108 link108 commented Aug 14, 2019

This PR is related to spinnaker/spinnaker#4181.

Different environments used to run Spinnaker have varying constraints, in order to ensure plugins can be used in all environments, we added the ability to download plugins via Kork at runtime. By default, downloading of plugin jars will be disabled, but if the Spinnaker administrator trusts the download location, they can enable plugin jar downloading. Spinnaker users (application owners) will not have the ability to add plugins.

To ensure Spinnaker is running as expected, a Spinnaker service will fail to start if it fails to download the plugin JARs.

@link108 link108 force-pushed the download-remote-plugins branch 2 times, most recently from 9c3cdc4 to 8b21165 Compare August 14, 2019 05:16
@link108 link108 changed the title Feat(plugins): Download remote plugins feat(plugins): Download remote plugins Aug 14, 2019
public boolean enabled;

static final String regex = "^[a-zA-Z0-9]+\\/[\\w-]+$";
static final Pattern pattern = Pattern.compile(regex);
Copy link
Member

Choose a reason for hiding this comment

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

Nit: Get rid of the regex property, since it's only used in one place.

Also, constants should be YELLING_SNAKE_CASE

Copy link
Member Author

Choose a reason for hiding this comment

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

removed these variables, now getting the matcher is just one line in the function 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually left pattern as a constant and updated its name, I think it helps readability

Copy link
Member

Choose a reason for hiding this comment

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

Not only readability, but compiling regex isn't free (and can produce exceptions) - which is why people assign its compiled into a static: If the compilation fails, it'll only fail once and at class creation, which is way better than at each invocation time.

@NoArgsConstructor
public class PluginConfigurationList {

@JsonProperty("pluginConfigurationList")
Copy link
Member

Choose a reason for hiding this comment

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

Personal preference, this: Remove List from the property name: It's just noise. Plural form for collections is preferable. No one in conversation says, "Check out my widget list," but instead they say, "Check out my widgets."

Copy link
Member

Choose a reason for hiding this comment

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

Also... is this class used anywhere? I don't see it being used.

Copy link
Member Author

Choose a reason for hiding this comment

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

heh, I had that same thought. I had refactored it out, but didn't remove the file from the PR, deleting now 👍

jarLocation | expected
"/opt/spinnaker/plugin/foo-bar-1.2.3.jar" | Paths.get("/opt/spinnaker/plugin/foo-bar-1.2.3.jar").toUri().toURL()
"file://example/com/foo-bar-1.2.3.jar" | new URL("file://example/com/foo-bar-1.2.3.jar")
"http://example.com/foo-bar-1.2.3.jar" | new URL("http://example.com/foo-bar-1.2.3.jar")
Copy link
Member

Choose a reason for hiding this comment

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

Would like to see a negative case in these tests as well.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added in another test. I was thinking of just having one or two test functions here, but I think having descriptive test names is good to show intent

Copy link
Contributor

@cfieber cfieber left a comment

Choose a reason for hiding this comment

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

Can you elaborate a bit on the use case for this? Adding plugins feels like an installation time concern, and the ability to via misconfiguration or malicious configuration fetch and execute remote code feels like it would need some pretty hefty justification to counter the potential for security vulnerabilities that it could introduce.


public void validate() {
Matcher matcher = pattern.matcher(name);
if (!matcher.find()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

could use matcher.matches here I believe

Copy link
Member Author

Choose a reason for hiding this comment

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

I changed to use matches() instead of find(), they're similar, but by using matches(), I'm able to simplify the pattern 👍

@link108 link108 force-pushed the download-remote-plugins branch 2 times, most recently from 9e615e0 to 52f2140 Compare August 15, 2019 02:17
@link108
Copy link
Member Author

link108 commented Aug 15, 2019

@cfieber Of course, happy to do so 👍

Most of the Spinnaker administrators I've talked to run, or want to run, Spinnaker in Kubernetes, where each microservice is one or more pods running the same Docker image. A pod is a grouping of docker images that all run on the same host. Spinnaker micro services run with one docker image per pod, the application itself, eg Orca, Clouddriver, etc.

In order to get a Docker image onto a pod, Kubernetes will download it from a remote source. While the remote source could be Docker hub or another public registry, in practice, Docker images are hosted in a secure artifact store that Kubernetes has access to (such as Artifactory.) Since we need some way of getting the plugin code onto the pod, we can mimic the approach of downloading assets from a location that matches the administrators’ security preference.

Requiring Spinnaker administrators to create their own custom docker images that contain plugins raises the barrier of entry to use plugins at all. They would still have to host their custom Docker images somewhere for Kubernetes to download and run. Since downloading assets is common when running Kubernetes and most Spinnaker administrators I've spoken with already host a secure artifact store, it would be a natural location to host plugin artifacts as well.

At most sites, Spinnaker administrators are trusted to ensure configuration is valid and not malicious. Additionally, ingress/egress is most often controlled at the infrastructure level with security groups, firewall rules, etc. Taking all of that into consideration and combining it with this feature being disabled by default, the administrator would have to purposefully introduce risk to their network.

Looking at other projects in the industry, this seems like an accepted practice. For example, in Jenkins, administrators can download plugins from a remote source at runtime.

Ideally Halyard would deliver plugins to Kubernetes directly, however there is a 1 MB limit on the Kubernetes side, so it won't work as plugins may be larger than 1 MB.

Do note, this is opt in only, so you have to manually enable the feature. The Halyard command to configure this defaults to disabled (spinnaker/halyard#1393)

@cfieber
Copy link
Contributor

cfieber commented Aug 19, 2019

@link108 Thanks for the details - this feels okayish for now.

@robzienert robzienert merged commit 6f9ac2c into spinnaker:master Aug 19, 2019
@link108 link108 deleted the download-remote-plugins branch August 19, 2019 22:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants