Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/2.0' into JENKINS-33662
Browse files Browse the repository at this point in the history
Conflicts:
	core/src/main/java/jenkins/install/SetupWizard.java
  • Loading branch information
kohsuke committed Mar 21, 2016
2 parents e7f72f0 + c4fb608 commit 523aa08
Show file tree
Hide file tree
Showing 27 changed files with 1,131 additions and 559 deletions.
174 changes: 84 additions & 90 deletions core/src/main/java/jenkins/install/SetupWizard.java
Expand Up @@ -22,10 +22,7 @@
import org.kohsuke.stapler.StaplerResponse;

import hudson.BulkChange;
import hudson.model.Descriptor;
import hudson.model.User;
import hudson.model.UserProperty;
import hudson.model.UserPropertyDescriptor;
import hudson.FilePath;
import hudson.security.FullControlOnceLoggedInAuthorizationStrategy;
import hudson.security.HudsonPrivateSecurityRealm;
import hudson.security.SecurityRealm;
Expand All @@ -34,10 +31,28 @@
import hudson.util.PluginServletFilter;
import jenkins.model.Jenkins;
import jenkins.security.s2m.AdminWhitelistRule;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.IOException;
import java.util.Locale;
import java.util.UUID;
import java.util.logging.Logger;

/**
* A Jenkins instance used during first-run to provide a limited set of services while
* initial installation is in progress
*
* @since 2.0
*/
public class SetupWizard {
/**
Expand All @@ -47,62 +62,68 @@ public class SetupWizard {

private final Logger LOGGER = Logger.getLogger(SetupWizard.class.getName());

public SetupWizard(Jenkins j) throws IOException {
User admin;
private final Jenkins jenkins;

public SetupWizard(Jenkins j) throws IOException, InterruptedException {
this.jenkins = j;
// Create an admin user by default with a
// difficult password
FilePath iapf = getInitialAdminPasswordFile();
if(j.getSecurityRealm() == null || j.getSecurityRealm() == SecurityRealm.NO_AUTHENTICATION) { // this seems very fragile
BulkChange bc = new BulkChange(j);

HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null);
j.setSecurityRealm(securityRealm);
String randomUUID = UUID.randomUUID().toString().replace("-", "").toLowerCase(Locale.ENGLISH);
admin = securityRealm.createAccount(SetupWizard.initialSetupAdminUserName, randomUUID);
admin.addProperty(new SetupWizard.AuthenticationKey(randomUUID));

// Lock Jenkins down:
FullControlOnceLoggedInAuthorizationStrategy authStrategy = new FullControlOnceLoggedInAuthorizationStrategy();
authStrategy.setAllowAnonymousRead(false);
j.setAuthorizationStrategy(authStrategy);
try{
HudsonPrivateSecurityRealm securityRealm = new HudsonPrivateSecurityRealm(false, false, null);
j.setSecurityRealm(securityRealm);
String randomUUID = UUID.randomUUID().toString().replace("-", "").toLowerCase(Locale.ENGLISH);

// Shut down all the ports we can by default:
j.setSlaveAgentPort(-1); // -1 to disable

// require a crumb issuer
j.setCrumbIssuer(new DefaultCrumbIssuer(false));

// set master -> slave security:
j.getInjector().getInstance(AdminWhitelistRule.class)
.setMasterKillSwitch(false);
// create an admin user
securityRealm.createAccount(SetupWizard.initialSetupAdminUserName, randomUUID);

// JENKINS-33599 - write to a file in the jenkins home directory
// most native packages of Jenkins creates a machine user account 'jenkins' to run Jenkins,
// and use group 'jenkins' for admins. So we allo groups to read this file
iapf.write(randomUUID, "UTF-8");
iapf.chmod(0640);

// Lock Jenkins down:
FullControlOnceLoggedInAuthorizationStrategy authStrategy = new FullControlOnceLoggedInAuthorizationStrategy();
authStrategy.setAllowAnonymousRead(false);
j.setAuthorizationStrategy(authStrategy);

// Shut down all the ports we can by default:
j.setSlaveAgentPort(-1); // -1 to disable

// require a crumb issuer
j.setCrumbIssuer(new DefaultCrumbIssuer(false));

// set master -> slave security:
j.getInjector().getInstance(AdminWhitelistRule.class)
.setMasterKillSwitch(false);

try{
j.save(); // !!
} finally {
bc.commit();
} finally {
bc.abort();
}
}
else {
admin = j.getUser(SetupWizard.initialSetupAdminUserName);
}

String setupKey = null;
if(admin != null && admin.getProperty(SetupWizard.AuthenticationKey.class) != null) {
setupKey = admin.getProperty(SetupWizard.AuthenticationKey.class).getKey();
}
if(setupKey != null) {
LOGGER.info("\n\n*************************************************************\n"
+ "*************************************************************\n"
+ "*************************************************************\n"
+ "\n"
+ "Jenkins initial setup is required. A security token is required to proceed. \n"
+ "Please use the following security token to proceed to installation: \n"
+ "\n"
+ "" + setupKey + "\n"
+ "\n"
+ "*************************************************************\n"
+ "*************************************************************\n"
+ "*************************************************************\n");
}
String setupKey = iapf.readToString().trim();

LOGGER.info("\n\n*************************************************************\n"
+ "*************************************************************\n"
+ "*************************************************************\n"
+ "\n"
+ "Jenkins initial setup is required. An admin user has been created and"
+ "a password generated. \n"
+ "Please use the following password to proceed to installation: \n"
+ "\n"
+ "" + setupKey + "\n"
+ "\n"
+ "This may also be found at: " + iapf.getRemote() + "\n"
+ "\n"
+ "*************************************************************\n"
+ "*************************************************************\n"
+ "*************************************************************\n");

try {
PluginServletFilter.addFilter(FORCE_SETUP_WIZARD_FILTER);
Expand All @@ -111,61 +132,34 @@ public SetupWizard(Jenkins j) throws IOException {
}
}

/**
* Gets the file used to store the initial admin password
*/
@Restricted(NoExternalUse.class) // use by Jelly
public FilePath getInitialAdminPasswordFile() {
return jenkins.getRootPath().child("secrets/initialAdminPassword");
}

/**
* Remove the setupWizard filter, ensure all updates are written to disk, etc
*/
public HttpResponse doCompleteInstall(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
Jenkins j = Jenkins.getActiveInstance();
User u = j.getUser("admin");
// JENKINS-33572 - without creating a new 'admin' user, auth key erroneously remained
if(u != null && u.getProperty(AuthenticationKey.class) != null) {
// There must be a better way of removing things...
Iterator<Map.Entry<Descriptor<UserProperty>,UserProperty>> entries = u.getProperties().entrySet().iterator();
while(entries.hasNext()) {
Map.Entry<?, ?> entry = entries.next();
if(entry.getValue() instanceof AuthenticationKey) {
entries.remove();
}
}
u.save();
}
j.setInstallState(InstallState.INITIAL_SETUP_COMPLETED);
public HttpResponse doCompleteInstall() throws IOException, ServletException {
jenkins.setInstallState(InstallState.INITIAL_SETUP_COMPLETED);
InstallUtil.saveLastExecVersion();
PluginServletFilter.removeFilter(FORCE_SETUP_WIZARD_FILTER);
// Also, clean up the setup wizard if it's completed
<<<<<<< HEAD
j.setSetupWizard(null);

UpgradeWizard uw = j.getInjector().getInstance(UpgradeWizard.class);
if (uw!=null)
uw.setCurrentLevel(new VersionNumber("2.0"));

=======
jenkins.setSetupWizard(null);
>>>>>>> origin/2.0
return HttpResponses.okJSON();
}

// Stores a user property for the authentication key, which is really the auto-generated user's password
public static class AuthenticationKey extends UserProperty {
String key;

public AuthenticationKey() {
}

public AuthenticationKey(String key) {
this.key = key;
}

public String getKey() {
return key;
}

public void setKey(String key) {
this.key = key;
}

@Override
public UserPropertyDescriptor getDescriptor() {
return null;
}
}

/**
* This filter will validate that the security token is provided
Expand Down
12 changes: 6 additions & 6 deletions core/src/main/resources/hudson/model/Job/configure.jelly
Expand Up @@ -27,14 +27,14 @@ 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" xmlns:i="jelly:fmt">
<l:layout title="${it.displayName} Config" cssClass="config j-hide-left" norefresh="true" permission="${it.EXTENDED_READ}">
<st:include page="sidepanel.jelly" />
<l:layout title="${it.displayName} Config" cssclass="main-panel-only" norefresh="true" permission="${it.EXTENDED_READ}">
<f:breadcrumb-config-outline />
<l:main-panel>
<l:js src="jsbundles/config-tabbar.js" />
<l:css src="jsbundles/jenkins-widgets.css" />
<div class="behavior-loading">${%loading}</div>
<f:form method="post" action="configSubmit" name="config" tableClass="job-config tabbed">
<l:js src="jsbundles/config-scrollspy.js" />
<l:css src="jsbundles/config-scrollspy.css" />

<div class="behavior-loading">${%LOADING}</div>
<f:form method="post" action="configSubmit" name="config" tableClass="config-table scrollspy">
<j:set var="descriptor" value="${it.descriptor}" />
<j:set var="instance" value="${it}" />

Expand Down
Expand Up @@ -16,22 +16,17 @@

<h1>${%Unlock Jenkins}</h1>
<p>
${%To ensure Jenkins is securely set up by the administrator, a setup security token has been printed to the logs.}
<small>
<a href="https://jenkins-ci.org/redirect/find-jenkins-logs" target="_blank">
${%Not sure where to find the logs?}
</a>
</small>
${%jenkins.install.findSecurityTokenMessage(it.initialAdminPasswordFile)}
</p>
<p>${%Please copy the token and paste it below.}</p>
<p>${%Please copy the password and paste it below.}</p>
<j:if test="${error}">
<div class="alert alert-danger">
<strong>${%ERROR:} </strong>
${%There is a problem with the security token, please check the logs for the correct token}
${%The password entered is incorrect, please check the file for the correct password}
</div>
</j:if>
<div class="form-group ${error ? 'has-error' : ''}">
<label class="control-label" for="security-token">${%Security token}</label>
<label class="control-label" for="security-token">${%Administrator password}</label>
<input name="j_username" value="${j.setupWizard.initialSetupAdminUserName}" type="hidden"/>
<input id="security-token" class="form-control" name="j_password"/>
<link rel="stylesheet" href="${j.installWizardPath}.css" type="text/css" />
Expand Down
@@ -0,0 +1,2 @@
jenkins.install.findSecurityTokenMessage=To ensure Jenkins is securely set up by the administrator, \
a password has been generated and written to the file on the Jenkins server here: <br/><small><code>{0}</code></small>
5 changes: 2 additions & 3 deletions core/src/main/resources/lib/form/select/select.js
Expand Up @@ -44,8 +44,7 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {

function hasChanged(selectEl, originalValue) {
// seems like a race condition allows this to fire before the 'selectEl' is defined. If that happens, exit..

if(!selectEl || !selectEl.options || !selectEl.options[0])
if(!selectEl || !selectEl.options || !selectEl.options.length > 0)
return false;
var firstValue = selectEl.options[0].value;
var selectedValue = selectEl.value;
Expand Down Expand Up @@ -83,4 +82,4 @@ Behaviour.specify("SELECT.select", 'select', 1000, function(e) {
}
});
});
});
});
12 changes: 3 additions & 9 deletions core/src/main/resources/lib/layout/layout.jelly
Expand Up @@ -38,7 +38,7 @@ THE SOFTWARE.
This is necessary for pages that include forms.
</st:attribute>
<st:attribute name="cssclass">
specify a css class name to include in the top level of this layout dom.
A css class name to include in the body element.
</st:attribute>
<st:attribute name="css" deprecated="true">
specify path that starts from "/" for loading additional CSS stylesheet.
Expand Down Expand Up @@ -99,13 +99,7 @@ ${h.initPageVariables(context)}
</j:if>
<link rel="shortcut icon" href="${resURL}/favicon.ico" type="image/vnd.microsoft.icon" />
<link rel="mask-icon" href="${rootURL}/images/mask-icon.svg" color="black" />

<!-- should the sidebar be hidden on load? -->
<style>
body.j-hide-left #side-panel{width:0; padding:0; overflow:hidden;}
body.j-hide-left #main-panel{margin:0 auto; max-width:75em;}
</style>


<!-- are we running as an unit test? -->
<script>var isRunAsTest=${h.isUnitTest}; var rootURL="${rootURL}"; var resURL="${resURL}";</script>

Expand Down Expand Up @@ -174,7 +168,7 @@ ${h.initPageVariables(context)}
<script src="${resURL}/jsbundles/page-init.js" type="text/javascript"/>

</head>
<body id="jenkins" class="yui-skin-sam jenkins-${h.version} ${attrs.cssClass}" data-version="jenkins-${h.version}" data-model-type="${it.class.name}">
<body id="jenkins" class="yui-skin-sam jenkins-${h.version} ${attrs.cssclass}" data-version="jenkins-${h.version}" data-model-type="${it.class.name}">
<!-- for accessibility, skip the entire navigation bar and etc and go straight to the head of the content -->
<a href="#skip2content" class="skiplink">Skip to content</a>

Expand Down
11 changes: 10 additions & 1 deletion war/gulpfile.js
Expand Up @@ -28,5 +28,14 @@ builder.bundle('src/main/js/pluginSetupWizard.js')
//
builder.bundle('src/main/js/config-tabbar.js')
.withExternalModuleMapping('jquery-detached', 'core-assets/jquery-detached:jquery2')
.less('src/main/js/widgets/jenkins-widgets.less')
.less('src/main/js/config-tabbar.less')
.inDir('src/main/webapp/jsbundles');

//
// Bundle the Config Scrollspy.
// See https://github.com/jenkinsci/js-builder#bundling
//
builder.bundle('src/main/js/config-scrollspy.js')
.withExternalModuleMapping('jquery-detached', 'core-assets/jquery-detached:jquery2')
.less('src/main/js/config-scrollspy.less')
.inDir('src/main/webapp/jsbundles');

0 comments on commit 523aa08

Please sign in to comment.