/core/src/main/java/jenkins/security/s2m/AdminWhitelistRule.java
Java | 229 lines | 186 code | 15 blank | 28 comment | 4 complexity | cb9a95af1c8d4ca87fbeb28a3f98df78 MD5 | raw file
- package jenkins.security.s2m;
- import hudson.Extension;
- import hudson.FilePath;
- import hudson.Functions;
- import hudson.Util;
- import hudson.util.HttpResponses;
- import jenkins.model.Jenkins;
- import jenkins.util.io.FileBoolean;
- import org.apache.commons.io.FileUtils;
- import org.jenkinsci.remoting.Role;
- import org.jenkinsci.remoting.RoleSensitive;
- import org.kohsuke.stapler.HttpResponse;
- import org.kohsuke.stapler.QueryParameter;
- import org.kohsuke.stapler.StaplerProxy;
- import org.kohsuke.stapler.StaplerRequest;
- import org.kohsuke.stapler.interceptor.RequirePOST;
- import java.io.BufferedReader;
- import java.io.ByteArrayInputStream;
- import java.io.ByteArrayOutputStream;
- import java.io.File;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.InputStreamReader;
- import java.io.PrintStream;
- import java.util.Collection;
- import java.util.Enumeration;
- import java.util.logging.Logger;
- import static java.util.logging.Level.*;
- /**
- * Rules of whitelisting for {@link RoleSensitive} objects and {@link FilePath}s.
- *
- * @author Kohsuke Kawaguchi
- */
- @Extension
- public class AdminWhitelistRule implements StaplerProxy {
- /**
- * Ones that we rejected but want to run by admins.
- */
- public final CallableRejectionConfig rejected;
- /**
- * Callables that admins have whitelisted explicitly.
- */
- public final CallableWhitelistConfig whitelisted;
- /**
- * FilePath access pattern rules specified by the admin
- */
- public final FilePathRuleConfig filePathRules;
- private final Jenkins jenkins;
- private boolean masterKillSwitch;
- public AdminWhitelistRule() throws IOException, InterruptedException {
- this.jenkins = Jenkins.getInstance();
- // while this file is not a secret, write access to this file is dangerous,
- // so put this in the better-protected part of $JENKINS_HOME, which is in secrets/
- // overwrite 30-default.conf with what we think is the best from the core.
- // this file shouldn't be touched by anyone. For local customization, use other files in the conf dir.
- // 0-byte file is used as a signal from the admin to prevent this overwriting
- placeDefaultRule(
- new File(jenkins.getRootDir(), "secrets/whitelisted-callables.d/default.conf"),
- getClass().getResourceAsStream("callable.conf"));
- placeDefaultRule(
- new File(jenkins.getRootDir(), "secrets/filepath-filters.d/30-default.conf"),
- transformForWindows(getClass().getResourceAsStream("filepath-filter.conf")));
- this.whitelisted = new CallableWhitelistConfig(
- new File(jenkins.getRootDir(),"secrets/whitelisted-callables.d/gui.conf"));
- this.rejected = new CallableRejectionConfig(
- new File(jenkins.getRootDir(),"secrets/rejected-callables.txt"),
- whitelisted);
- this.filePathRules = new FilePathRuleConfig(
- new File(jenkins.getRootDir(),"secrets/filepath-filters.d/50-gui.conf"));
- this.masterKillSwitch = loadMasterKillSwitchFile();
- }
- /**
- * Reads the master kill switch.
- *
- * Instead of {@link FileBoolean}, we use a text file so that the admin can prevent Jenkins from
- * writing this to file.
- */
- private boolean loadMasterKillSwitchFile() {
- File f = getMasterKillSwitchFile();
- try {
- if (!f.exists()) return true;
- return Boolean.parseBoolean(FileUtils.readFileToString(f).trim());
- } catch (IOException e) {
- LOGGER.log(WARNING, "Failed to read "+f, e);
- return false;
- }
- }
- private File getMasterKillSwitchFile() {
- return new File(jenkins.getRootDir(),"secrets/slave-to-master-security-kill-switch");
- }
- /**
- * Transform path for Windows.
- */
- private InputStream transformForWindows(InputStream src) throws IOException {
- BufferedReader r = new BufferedReader(new InputStreamReader(src));
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- PrintStream p = new PrintStream(out);
- String line;
- while ((line=r.readLine())!=null) {
- if (!line.startsWith("#") && Functions.isWindows())
- line = line.replace("/","\\\\");
- p.println(line);
- }
- p.close();
- return new ByteArrayInputStream(out.toByteArray());
- }
- private void placeDefaultRule(File f, InputStream src) throws IOException, InterruptedException {
- try {
- new FilePath(f).copyFrom(src);
- } catch (IOException e) {
- // we allow admins to create a read-only file here to block overwrite,
- // so this can fail legitimately
- if (!f.canWrite()) return;
- LOGGER.log(WARNING, "Failed to generate "+f,e);
- }
- }
- public boolean isWhitelisted(RoleSensitive subject, Collection<Role> expected, Object context) {
- if (masterKillSwitch)
- return true; // master kill switch is on. subsystem deactivated
- String name = subject.getClass().getName();
- if (whitelisted.contains(name))
- return true; // whitelisted by admin
- // otherwise record the problem and refuse to execute that
- rejected.report(subject.getClass());
- return false;
- }
- public boolean checkFileAccess(String op, File f) {
- // if the master kill switch is off, we allow everything
- if (masterKillSwitch)
- return true;
- return filePathRules.checkFileAccess(op, f);
- }
- @RequirePOST
- public HttpResponse doSubmit(StaplerRequest req) throws IOException {
- jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
- String whitelist = Util.fixNull(req.getParameter("whitelist"));
- if (!whitelist.endsWith("\n"))
- whitelist+="\n";
- Enumeration e = req.getParameterNames();
- while (e.hasMoreElements()) {
- String name = (String) e.nextElement();
- if (name.startsWith("class:")) {
- whitelist += name.substring(6)+"\n";
- }
- }
- whitelisted.set(whitelist);
- String newRules = Util.fixNull(req.getParameter("filePathRules"));
- filePathRules.parseTest(newRules); // test first before writing a potentially broken rules
- filePathRules.set(newRules);
- return HttpResponses.redirectToDot();
- }
- /**
- * Approves all the currently rejected subjects
- */
- @RequirePOST
- public HttpResponse doApproveAll() throws IOException {
- StringBuilder buf = new StringBuilder();
- for (Class c : rejected.get()) {
- buf.append(c.getName()).append('\n');
- }
- whitelisted.append(buf.toString());
- return HttpResponses.ok();
- }
- /**
- * Approves specific callables by their names.
- */
- @RequirePOST
- public HttpResponse doApprove(@QueryParameter String value) throws IOException {
- whitelisted.append(value);
- return HttpResponses.ok();
- }
- public boolean getMasterKillSwitch() {
- return masterKillSwitch;
- }
- public void setMasterKillSwitch(boolean state) {
- try {
- jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
- FileUtils.writeStringToFile(getMasterKillSwitchFile(),Boolean.toString(state));
- // treat the file as the canonical source of information in case write fails
- masterKillSwitch = loadMasterKillSwitchFile();
- } catch (IOException e) {
- LOGGER.log(WARNING, "Failed to write master kill switch", e);
- }
- }
- /**
- * Restricts the access to administrator.
- */
- @Override
- public Object getTarget() {
- jenkins.checkPermission(Jenkins.RUN_SCRIPTS);
- return this;
- }
- private static final Logger LOGGER = Logger.getLogger(AdminWhitelistRule.class.getName());
- }