Navigation Menu

Skip to content

Commit

Permalink
fix #1459 require 'run' access to server node for Local Command plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
gschueler committed Jun 24, 2016
1 parent c1a344c commit 59de10c
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 5 deletions.
5 changes: 5 additions & 0 deletions plugins/localexec-plugin/build.gradle
Expand Up @@ -13,9 +13,14 @@ jar {

apply plugin: 'idea'
apply plugin: 'maven'
apply plugin: 'groovy'

dependencies {
compile project(":core")

testCompile "org.codehaus.groovy:groovy-all:2.3.7"
testCompile "org.spockframework:spock-core:0.7-groovy-2.0"
testCompile "cglib:cglib-nodep:2.2.2"
}

task createPom << {
Expand Down
Expand Up @@ -24,15 +24,17 @@
*/
package com.dtolabs.rundeck.plugin.localexec;

import com.dtolabs.rundeck.core.cli.CLIUtils;
import com.dtolabs.rundeck.core.common.INodeEntry;
import com.dtolabs.rundeck.core.common.INodeSet;
import com.dtolabs.rundeck.core.common.NodeEntryImpl;
import com.dtolabs.rundeck.core.common.NodeSetImpl;
import com.dtolabs.rundeck.core.dispatcher.DataContextUtils;
import com.dtolabs.rundeck.core.execution.service.NodeExecutorResultImpl;
import com.dtolabs.rundeck.core.execution.workflow.steps.FailureReason;
import com.dtolabs.rundeck.core.execution.workflow.steps.StepFailureReason;
import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException;
import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepFailureReason;
import com.dtolabs.rundeck.core.plugins.Plugin;
import com.dtolabs.rundeck.core.utils.Converter;
import com.dtolabs.rundeck.core.utils.OptsUtil;
import com.dtolabs.rundeck.core.utils.ScriptExecUtil;
import com.dtolabs.rundeck.plugins.ServiceNameConstants;
Expand All @@ -41,9 +43,10 @@
import com.dtolabs.rundeck.plugins.step.NodeStepPlugin;
import com.dtolabs.rundeck.plugins.step.PluginStepContext;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.io.OutputStream;
import java.util.*;


/**
Expand All @@ -59,9 +62,53 @@ public class LocalExecNodeStepPlugin implements NodeStepPlugin {
@PluginProperty(title = "Command", description = "The command (runs locally)", required = true)
private String command;


public enum LocalExecReason implements FailureReason{
Unauthorized
}

/**
* interface for testing
*/
public interface LocalCommandRunner{
int runLocalCommand(
final String[] command,
final Map<String, String> envMap, final File workingdir,
final OutputStream outputStream, final OutputStream errorStream
) throws IOException, InterruptedException;
}

/**
* Use ScriptExecUtil
*/
private static class UtilRunner implements LocalCommandRunner {
@Override
public int runLocalCommand(
final String[] command,
final Map<String, String> envMap,
final File workingdir,
final OutputStream outputStream,
final OutputStream errorStream
) throws IOException, InterruptedException
{
return ScriptExecUtil.runLocalCommand(command, envMap, workingdir, outputStream, errorStream);
}
}

private LocalCommandRunner runner = new UtilRunner();

@Override
public void executeNodeStep(PluginStepContext context, Map<String, Object> map, INodeEntry entry)
throws NodeStepException {
//verify run authorization for local node
if(!hasLocalNodeRunAuthorization(context)){
throw new NodeStepException(
"Not authorized for \"run\" on local node \"" + context.getFramework().getFrameworkNodeName() + "\"",
LocalExecReason.Unauthorized,
entry.getNodename()
);
}

if(null==command || "".equals(command.trim())) {
throw new NodeStepException("Command is not set",
StepFailureReason.ConfigurationFailure,
Expand All @@ -82,7 +129,7 @@ public void executeNodeStep(PluginStepContext context, Map<String, Object> map,
Map<String, String> env = DataContextUtils.generateEnvVarsFromContext(nodeData);
final int result;
try {
result = ScriptExecUtil.runLocalCommand(finalCommand, env, null, System.out, System.err);
result = runner.runLocalCommand(finalCommand, env, null, System.out, System.err);
if(result!=0) {
Map<String,Object> failureData=new HashMap<>();
failureData.put(NodeExecutorResultImpl.FAILURE_DATA_RESULT_CODE, result);
Expand All @@ -98,4 +145,36 @@ public void executeNodeStep(PluginStepContext context, Map<String, Object> map,
throw new NodeStepException(e, StepFailureReason.Interrupted, entry.getNodename());
}
}

/**
* @param context context
* @return true if run is authorized for local node
*/
private boolean hasLocalNodeRunAuthorization(final PluginStepContext context) {
NodeEntryImpl frameworkNode = context.getFramework().getFrameworkNodes().createFrameworkNode();
NodeSetImpl nodeset = new NodeSetImpl();
nodeset.putNode(frameworkNode);

INodeSet run = context.getFramework().filterAuthorizedNodes(
context.getFrameworkProject(),
Collections.singleton("run"),
nodeset,
context.getExecutionContext().getAuthContext()
);
return run.getNode(frameworkNode.getNodename()) != null;
}
public LocalCommandRunner getRunner() {
return runner;
}

public void setRunner(LocalCommandRunner runner) {
this.runner = runner;
}
public String getCommand() {
return command;
}

public void setCommand(String command) {
this.command = command;
}
}
@@ -0,0 +1,112 @@
package com.dtolabs.rundeck.plugin.localexec

import com.dtolabs.rundeck.core.authorization.AuthContext
import com.dtolabs.rundeck.core.common.FilesystemFramework
import com.dtolabs.rundeck.core.common.Framework
import com.dtolabs.rundeck.core.common.IFrameworkNodes
import com.dtolabs.rundeck.core.common.IFrameworkProjectMgr
import com.dtolabs.rundeck.core.common.IFrameworkServices
import com.dtolabs.rundeck.core.common.NodeEntryImpl
import com.dtolabs.rundeck.core.common.NodeSetImpl
import com.dtolabs.rundeck.core.execution.ExecutionContext
import com.dtolabs.rundeck.core.execution.workflow.steps.node.NodeStepException
import com.dtolabs.rundeck.core.utils.IPropertyLookup
import com.dtolabs.rundeck.plugins.PluginLogger
import com.dtolabs.rundeck.plugins.step.PluginStepContext
import spock.lang.Specification

/**
* Created by greg on 6/24/16.
*/
class LocalExecNodeStepPluginSpec extends Specification {
File testDir
File projectsDir

def setup() {
testDir = File.createTempFile("LocalExecNodeStepPluginSpec", "-test")
projectsDir = File.createTempFile("LocalExecNodeStepPluginSpec", "-test")

}

def cleanup() {
testDir.delete()
projectsDir.delete()
}

def "no run authorization for local node throws exception"() {
given:
def plugin = new LocalExecNodeStepPlugin()
plugin.setCommand("a command")

def context = Mock(PluginStepContext)
def config = [:]
def node = new NodeEntryImpl('anode')
def fwknode = new NodeEntryImpl('fwknode')

def fwkNodes = Mock(IFrameworkNodes)
def fwk = testFramework(fwkNodes)
println testDir

when:
plugin.executeNodeStep(context, config, node)

then:
NodeStepException e = thrown()
e.nodeName == 'anode'
e.message ==~ /^Not authorized for "run" on local node .+/
3 * context.getFramework() >> fwk
1 * context.getFrameworkProject() >> 'aproject'
1 * context.getExecutionContext() >> Mock(ExecutionContext) {
1 * getAuthContext() >> Mock(AuthContext)
}
1 * fwkNodes.createFrameworkNode() >> fwknode
1 * fwkNodes.getFrameworkNodeName() >> 'fwknode'
1 * fwkNodes.filterAuthorizedNodes('aproject', { it.contains('run') }, _, _) >> new NodeSetImpl()

}

def "run authorization for local will execute"() {
given:
def plugin = new LocalExecNodeStepPlugin()
plugin.setCommand("a command")

def runner = Mock(LocalExecNodeStepPlugin.LocalCommandRunner)
plugin.setRunner(runner)


def context = Mock(PluginStepContext) {
getLogger() >> Mock(PluginLogger)
}
def config = [:]
def node = new NodeEntryImpl('anode')
def fwknode = new NodeEntryImpl('fwknode')
def filtered = new NodeSetImpl([fwknode: fwknode])

def fwkNodes = Mock(IFrameworkNodes)
def fwk = testFramework(fwkNodes)
println testDir

when:
plugin.executeNodeStep(context, config, node)

then:
2 * context.getFramework() >> fwk
1 * context.getFrameworkProject() >> 'aproject'
1 * context.getExecutionContext() >> Mock(ExecutionContext) {
1 * getAuthContext() >> Mock(AuthContext)
}
1 * fwkNodes.createFrameworkNode() >> fwknode
1 * fwkNodes.filterAuthorizedNodes('aproject', { it.contains('run') }, _, _) >> filtered
1 * runner.runLocalCommand(_, _, _, _, _) >> 0
}

private Framework testFramework(IFrameworkNodes fwkNodes) {
new Framework(
Mock([constructorArgs: [testDir, projectsDir]], FilesystemFramework),
Mock(IFrameworkProjectMgr),
Mock(IPropertyLookup),
Mock(IFrameworkServices),
fwkNodes
)
}
}

0 comments on commit 59de10c

Please sign in to comment.