Skip to content

Commit

Permalink
Merge pull request #16 from stephenc/jenkins-32770
Browse files Browse the repository at this point in the history
[JENKINS-32770] Provide a mechanism to run specific projects as ACL.SYSTEM
  • Loading branch information
ikedam committed Feb 11, 2016
2 parents 43af1ff + 0525273 commit 12a36be
Show file tree
Hide file tree
Showing 16 changed files with 1,076 additions and 0 deletions.
@@ -0,0 +1,227 @@
/*
* The MIT License
*
* Copyright (c) 2013-2016 Stephen Connolly, IKEDA Yasuyuki
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.authorizeproject.strategy;

import hudson.Extension;
import hudson.model.Descriptor;
import hudson.model.Job;
import hudson.model.Queue;
import hudson.security.ACL;
import hudson.util.FormValidation;
import java.io.IOException;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectProperty;
import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectStrategy;
import org.jenkinsci.plugins.authorizeproject.AuthorizeProjectStrategyDescriptor;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;

/**
* Run builds as {@link ACL#SYSTEM}. Using this strategy becomes important when
* {@link org.jenkinsci.plugins.authorizeproject.GlobalQueueItemAuthenticator}
* is forcing jobs to a user other than {@link ACL#SYSTEM}.
*
* @since 1.1.1
*/
public class SystemAuthorizationStrategy extends AuthorizeProjectStrategy {

@DataBoundConstructor
public SystemAuthorizationStrategy() {
}

/**
* {@inheritDoc}
*/
@Override
public Authentication authenticate(Job<?, ?> job, Queue.Item item) {
return ACL.SYSTEM;
}

/**
* Return {@link SystemAuthorizationStrategy} configured in a job.
*
* @param job the {@link Job}
* @return the {@link SystemAuthorizationStrategy} or {@code null}
*/
protected static SystemAuthorizationStrategy getCurrentStrategy(Job<?, ?> job) {
if (job == null) {
return null;
}

AuthorizeProjectProperty prop = job.getProperty(AuthorizeProjectProperty.class);
if (prop == null) {
return null;
}

AuthorizeProjectStrategy strategy = prop.getStrategy();
if (!(strategy instanceof SystemAuthorizationStrategy)) {
return null;
}

return (SystemAuthorizationStrategy) strategy;
}

/**
* Called when XSTREAM2 instantiates this from XML configuration.
*
* When configured via REST/CLI, {@link Descriptor#newInstance(StaplerRequest, JSONObject)} is not called.
* Instead checks authentication here.
*
* @return return myself.
* @throws IOException authentication failed.
*/
private Object readResolve() throws IOException {
Jenkins instance = Jenkins.getInstance();
if (instance == null || !instance.hasPermission(Jenkins.RUN_SCRIPTS)) {
// This is called via REST/CLI.
// As REST/CLI interface saves configuration after successfully load object from the XML,
// this prevents the new configuration saved.
throw new IOException(Messages.SystemAuthorizationStrategy_readResolve());
}
return this;
}

/**
* For now we are an object with no configurable fields, so return a fixed value.
* If we add configurable fields we probably should consider removing the final.
*
* @return our hashCode.
*/
@Override
public final int hashCode() {
return SystemAuthorizationStrategy.class.hashCode();
}

/**
* For now we are an object with no configurable fields, so strict instanceof establishes equality.
* If we add configurable fields we probably should consider removing the final.
*
* @param obj the object to test equality with.
* @return {@code true} if and only if this is a equivalent {@link SystemAuthorizationStrategy} instance.
*/
@Override
public final boolean equals(Object obj) {
return obj != null && SystemAuthorizationStrategy.class == obj.getClass();
}

/**
* Our descriptor
*/
@Extension(ordinal = -100)
public static class DescriptorImpl extends AuthorizeProjectStrategyDescriptor {

/**
* Flag to mark where changing a job using this strategy requires administrator permissions.
*/
private boolean permitReconfiguration;

/**
* @return the name shown in project configuration pages.
* @see hudson.model.Descriptor#getDisplayName()
*/
@Override
public String getDisplayName() {
return Messages.SystemAuthorizationStrategy_DisplayName();
}

/**
* {@inheritDoc}
*/
@Override
public boolean isEnabledByDefault() {
return false;
}

/**
* Gets the flag to mark where changing a job using this strategy requires administrator permissions.
*
* @return {@code true} if non-admins are allowed to modify jobs that are using this strategy.
*/
public boolean isPermitReconfiguration() {
return permitReconfiguration;
}

/**
* Sets the flag to mark where changing a job using this strategy requires administrator permissions.
*
* @param permitReconfiguration {@code true} if non-admins are allowed to modify jobs that are using this strategy.
*/
public void setPermitReconfiguration(boolean permitReconfiguration) {
if (permitReconfiguration != this.permitReconfiguration) {
this.permitReconfiguration = permitReconfiguration;
save();
}
}

/**
* Tests if an object is a {@link Job}
*
* @param it the object.
* @return {@code true} if and only if the supplied object is a {@link Job}
*/
@Restricted(NoExternalUse.class) // helper method for Jelly EL
public boolean isJob(Object it) {
return it instanceof Job;
}

/**
* {@inheritDoc}
*/
@Override
public void configureFromGlobalSecurity(StaplerRequest req, JSONObject js) throws FormException {
setPermitReconfiguration(js.getBoolean("permitReconfiguration"));
}

/**
* {@inheritDoc}
*/
@Override
public SystemAuthorizationStrategy newInstance(StaplerRequest req, JSONObject formData)
throws FormException {
SystemAuthorizationStrategy result = (SystemAuthorizationStrategy) super.newInstance(req, formData);
Jenkins instance = Jenkins.getInstance();
if (instance == null || !instance.hasPermission(Jenkins.RUN_SCRIPTS)) {
Job job = req.findAncestorObject(Job.class);
if (job != null) {
if (!(permitReconfiguration && getCurrentStrategy(job) != null)) {
throw new FormException(Messages.SystemAuthorizationStrategy_administersOnly(), "strategy");
}
}
}
return result;
}

public FormValidation doCheckPermitReconfiguration(String value) {
if (!Boolean.parseBoolean(value)) {
return FormValidation.warning(Messages.SystemAuthorizationStrategy_administersOnly());
}
return FormValidation.warning(Messages.SystemAuthorizationStrategy_userConfigurable());
}
}
}
Expand Up @@ -30,3 +30,7 @@ SpecificUsersAuthorizationStrategy.password.required=Required
SpecificUsersAuthorizationStrategy.password.invalid=Failed to authenticate
SpecificUsersAuthorizationStrategy.noNeedReauthentication.usage=This feature can cause a security problem. Please check CONFIGURE privilege of this project is granted only to proper users.
AnonymousAuthorizationStrategy.DisplayName=Run as anonymous
SystemAuthorizationStrategy.DisplayName=Run as SYSTEM
SystemAuthorizationStrategy.administersOnly=Only Administrators can configure the Run as SYSTEM strategy
SystemAuthorizationStrategy.userConfigurable=Non-administrators will be able to re-purpose jobs configured with this strategy
SystemAuthorizationStrategy.readResolve=Failed to authenticate to run builds as SYSTEM. In REST/CLI interface, you must be an administrator.
@@ -0,0 +1,55 @@
<!--
The MIT License
Copyright (c) 2016 Stephen Connolly
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<j:choose>
<j:when test="${!descriptor.isJob(it)}">
<!-- not a job so don't give job specific notices -->
</j:when>
<j:when test="${descriptor.permitReconfiguration and app.hasPermission(app.RUN_SCRIPTS)}">
<f:block>
<div class="warning">
${%Any non-administrators with permission to configure this job can retain this strategy while otherwise reconfiguring this job}
</div>
</f:block>
</j:when>
<j:when test="${instance == null and app.hasPermission(app.RUN_SCRIPTS)}">
<f:block>
<div class="warning">
${%Only users with administrator permissions will be able to reconfigure this job while retaining this strategy}
</div>
</f:block>
</j:when>
<j:when test="${instance == null}">
<f:block>
<div class="${it.hasPermission(app.RUN_SCRIPTS)?'warning':'error'}">
${%Only users with administrator permissions can select this strategy}
</div>
</f:block>
</j:when>
<j:otherwise>
<!-- don't advertise to the user that they can use this job to run as SYSTEM -->
</j:otherwise>
</j:choose>
</j:jelly>
@@ -0,0 +1,30 @@
<!--
The MIT License
Copyright (c) 2016 Stephen Connolly
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-->
<?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout"
xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<f:entry title="${%Allow non-administrators to reconfiguration jobs already configured with this strategy}" field="permitReconfiguration">
<f:checkbox/>
</f:entry>
</j:jelly>
@@ -0,0 +1,11 @@
<div>
When enabled, regular users will be able to change the configuration of jobs that use this strategy.
This may open up a security risk as the users could potentially configure the job to run scripts
which would enable an ecalation of privilidges attack.
<br />
When disabled, only users with the <code>RUN_SCRIPTS</code> permisson will be able to either select
this strategy or reconfigure a job that uses this strategy.
<br />
<strong>NOTE:</strong> The RUN_SCRIPTS permission is the highest permission available in Jenkins.
By default, all administator users will have the RUN_SCRIPTS permission.
</div>
@@ -0,0 +1,3 @@
<div>
Run a build as the SYSTEM user.
</div>

0 comments on commit 12a36be

Please sign in to comment.