Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,32 @@ If the workspace is removed, the tag that was applied is lost.
Tagging a workspace made sense when using centralized repositories that automatically applied the tag to the centralized repository.
Applying a git tag in an agent workspace doesn't have many practical uses.

[#security-configuration]
=== Security Configuration

image:/images/git-security-configuration.png[Security Configuration]

In the `Configure Global Security` page, the Git Plugin provides the following option:

[[global-security-git-hooks]]
Git Hooks::

link:https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks[Git hooks] allow scripts to be invoked when certain important git repository actions occur.
This configuration controls the execution of client-side hooks on the controller and on agents.
It is recommended that git hooks be **disabled** on the controller and on agents.
+
Most git repositories do not use hooks in the repository and do not need repository hooks.
In those rare cases where repository hooks are needed, it is highly recommended that they are **disabled** on the Jenkins controller and on Jenkins agents.
+
Client-side hooks are **not** copied when the repository is cloned by Jenkins using the inbuilt SCM methods.
However, client-side hooks might be installed in a repository by build steps or by misconfiguration.
+
If hook scripts are allowed, a client-side hook script installed in a repository will execute when the matching git operation is performed.
For example, if hooks are allowed and a git repository includes a `post-checkout` hook, the hook script will run after any checkout in that repository.
Copy link
Member

Choose a reason for hiding this comment

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

depending on:

  • git-client because it won't always when using the jgit provider on windows, hooks are run on a posix file system, or if you are on a windows filesystem and cygwin or some other sh.exe is detected.

If hooks are allowed and a git repository includes a `pre-auto-gc` hook, the hook script will run before any automatic git garbage collection task.
+
See link:https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks["Customizing Git - Git Hooks"] for more details about git repository hooks.

[#repository-browser]
=== Repository Browser

Expand Down
Binary file added images/git-security-configuration.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 1 addition & 7 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
<spotbugs.effort>Max</spotbugs.effort>
<spotbugs.failOnError>true</spotbugs.failOnError>
<spotbugs.threshold>Medium</spotbugs.threshold>
<forkCount>0.8C</forkCount>
</properties>

<dependencyManagement>
Expand Down Expand Up @@ -340,13 +341,6 @@
</dependency>
</dependencies>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>0.8C</forkCount>
<reuseForks>true</reuseForks>
</configuration>
</plugin>
</plugins>
</build>

Expand Down
3 changes: 3 additions & 0 deletions src/main/java/hudson/plugins/git/GitSCM.java
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import hudson.util.FormValidation;
import hudson.util.ListBoxModel;
import jenkins.model.Jenkins;
import jenkins.plugins.git.GitHooksConfiguration;
import jenkins.plugins.git.GitSCMMatrixUtil;
import jenkins.plugins.git.GitToolChooser;
import jenkins.util.SystemProperties;
Expand Down Expand Up @@ -800,6 +801,7 @@ private PollingResult compareRemoteRevisionWithImpl(Job<?, ?> project, Launcher
GitClient git = createClient(listener, environment, lastBuild, node, workingDirectory);

if (git.hasGitRepo(false)) {
GitHooksConfiguration.configure(git);
// Repo is there - do a fetch
listener.getLogger().println("Fetching changes from the remote Git repositories");

Expand Down Expand Up @@ -1234,6 +1236,7 @@ private void retrieveChanges(Run build, GitClient git, TaskListener listener) th
throw new AbortException("Error cloning remote repo '" + rc.getName() + "'");
}
}
GitHooksConfiguration.configure(git);

for (RemoteConfig remoteRepository : repos) {
if (remoteRepository.equals(repos.get(0)) && removeSecondFetch){
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/jenkins/plugins/git/AbstractGitSCMSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,8 @@ private <T, C extends GitSCMSourceContext<C, R>, R extends GitSCMSourceRequest>
listener.getLogger().println("Creating git repository in " + cacheDir);
client.init();
}
GitHooksConfiguration.configure(client, GitHooksConfiguration.get().isAllowedOnController());

String remoteName = context.remoteName();
listener.getLogger().println("Setting " + remoteName + " to " + getRemote());
client.setRemoteUrl(remoteName, getRemote());
Expand Down
141 changes: 141 additions & 0 deletions src/main/java/jenkins/plugins/git/GitHooksConfiguration.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
/*
* The MIT License
*
* Copyright (c) 2021 CloudBees, Inc.
*
* 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 jenkins.plugins.git;

import edu.umd.cs.findbugs.annotations.NonNull;
import hudson.Extension;
import hudson.Functions;
import hudson.model.PersistentDescriptor;
import hudson.remoting.Channel;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.jenkinsci.Symbol;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

import java.io.IOException;
import java.util.logging.Logger;



@Extension @Symbol("gitHooks") @Restricted(NoExternalUse.class)
public class GitHooksConfiguration extends GlobalConfiguration implements PersistentDescriptor {

public static final String DISABLED_WIN = "NUL:";
public static final String DISABLED_NIX = "/dev/null";
static final Logger LOGGER = Logger.getLogger(GitHooksConfiguration.class.getName());

private boolean allowedOnController = false;
private boolean allowedOnAgents = false;

@NonNull
public static GitHooksConfiguration get() {
final GitHooksConfiguration configuration = GlobalConfiguration.all().get(GitHooksConfiguration.class);
if (configuration == null) {
throw new IllegalStateException("[BUG] No configuration registered, make sure not running on an agent or that Jenkins has started properly.");
}
return configuration;
}

public boolean isAllowedOnController() {
return allowedOnController;
}

public void setAllowedOnController(final boolean allowedOnController) {
this.allowedOnController = allowedOnController;
save();
}

public boolean isAllowedOnAgents() {
return allowedOnAgents;
}

public void setAllowedOnAgents(final boolean allowedOnAgents) {
this.allowedOnAgents = allowedOnAgents;
save();
}

@Override @NonNull
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
}

public static void configure(GitClient client) throws IOException, InterruptedException {
final GitHooksConfiguration configuration = GitHooksConfiguration.get();
configure(client, configuration.isAllowedOnController(), configuration.isAllowedOnAgents());
}

public static void configure(GitClient client, final boolean allowedOnController, final boolean allowedOnAgents) throws IOException, InterruptedException {
if (Channel.current() == null) {
//Running on controller
try (Repository ignored = client.getRepository()){
//That went well, so the code runs on the controller and the repo is local
configure(client, allowedOnController);
} catch (UnsupportedOperationException e) {
// Client represents a remote repository, so this code runs on the controller but the repo is on an agent
configure(client, allowedOnAgents);
}
} else {
//Running on agent
configure(client, allowedOnAgents);
}
}

public static void configure(GitClient client, final boolean allowed) throws IOException, InterruptedException {
if (!allowed) {
client.withRepository((repo, channel) -> {
disable(repo);
return null;
});
} else {
client.withRepository((repo, channel) -> {
unset(repo);
return null;
});
}
}

private static void unset(final Repository repo) throws IOException {
final StoredConfig repoConfig = repo.getConfig();
final String val = repoConfig.getString("core", null, "hooksPath");
if (!StringUtils.isEmpty(val) && !(DISABLED_NIX.equals(val) || DISABLED_WIN.equals(val))) {
LOGGER.warning(() -> String.format("core.hooksPath explicitly set to %s and will be left intact on %s.", val, repo.getDirectory()));
} else {
repoConfig.unset("core", null, "hooksPath");
repoConfig.save();
}
}

private static void disable(final Repository repo) throws IOException {
final String VAL = Functions.isWindows() ? DISABLED_WIN : DISABLED_NIX;
final StoredConfig repoConfig = repo.getConfig();
repoConfig.setString("core", null, "hooksPath", VAL);
repoConfig.save();
}
}
2 changes: 2 additions & 0 deletions src/main/java/jenkins/plugins/git/GitSCMFileSystem.java
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ public SCMFileSystem build(@NonNull Item owner, @NonNull SCM scm, @CheckForNull
listener.getLogger().println("Creating git repository in " + cacheDir);
client.init();
}
GitHooksConfiguration.configure(client, GitHooksConfiguration.get().isAllowedOnController());
String remoteName = StringUtils.defaultIfBlank(config.getName(), Constants.DEFAULT_REMOTE_NAME);
listener.getLogger().println("Setting " + remoteName + " to " + remote);
client.setRemoteUrl(remoteName, remote);
Expand Down Expand Up @@ -396,6 +397,7 @@ public SCMFileSystem build(@NonNull SCMSource source, @NonNull SCMHead head, @Ch
listener.getLogger().println("Creating git repository in " + cacheDir);
client.init();
}
GitHooksConfiguration.configure(client, GitHooksConfiguration.get().isAllowedOnController());
String remoteName = builder.remoteName();
listener.getLogger().println("Setting " + remoteName + " to " + gitSCMSource.getRemote());
client.setRemoteUrl(remoteName, gitSCMSource.getRemote());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?jelly escape-by-default='true'?>
<!--
~ The MIT License
~
~ Copyright (c) 2021, CloudBees, Inc.
~
~ 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.
-->
<j:jelly xmlns:j="jelly:core" xmlns:f="/lib/form">
<f:section title="${%Git Hooks}">
<f:entry field="allowedOnController"
description="${%Allow git hooks to run on the Jenkins Controller}">
<f:checkbox title="${%Allow on Controller}" />
</f:entry>
<f:entry field="allowedOnAgents"
description="${%Allow git hooks to run on Jenkins Agents}">
<f:checkbox title="${%Allow on Agents}" />
</f:entry>
</f:section>
</j:jelly>
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<div>
<p>
<a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">Git hooks</a> allow scripts to be invoked when certain important git repository actions occur.
This configuration controls the execution of client-side hooks on <i>Jenkins agents</i>.
It is recommended that git hooks be <strong>disabled</strong> on Jenkins agents.
</p>
<p>
Most git repositories do not use hooks in the repository and do not need repository hooks.
In those rare cases where repository hooks are needed, it is highly recommended that they are <strong>disabled</strong> on the Jenkins controller and on Jenkins agents.
</p>
<p>
Client-side hooks are <strong>not</strong> copied when the repository is cloned.
However, client-side hooks might be installed in a repository by build steps or by misconfiguration.
</p>
<p>
If hook scripts are allowed on agents, a client-side hook script installed in a repository on a Jenkins agent will execute when the matching git operation is performed.
For example, if hooks are allowed on agents and a git repository on an agent includes a <code>post-checkout</code> hook, the hook script will run on the agent after any checkout in that repository.
If hooks are allowed on agents and a git repository on an agent includes a <code>pre-auto-gc</code> hook, the hook script will run on the agent before any automatic git garbage collection task.
</p>
<p>
See <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">"Customizing Git - Git Hooks"</a> for more details about git repository hooks.
</p>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div>
<p>
<a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">Git hooks</a> allow scripts to be invoked when certain important git repository actions occur.
This configuration controls the execution of client-side hooks on the <i>Jenkins controller</i>.
It is recommended that git hooks be <strong>disabled</strong> on the controller.
</p>
<p>
The Jenkins controller uses git repositories to checkout Pipeline definitions, to detect changes in remote repositories, and to cache Pipeline shared libraries.
Jenkins jobs that run on the controller may use git repositories in many other ways.
It is strongly recommended that jobs are not run on the Jenkins controller.
Refer to the <a href="https://www.jenkins.io/doc/book/security/controller-isolation/#not-building-on-the-built-in-node">controller isolation documentation</a> for more details.
</p>
<p>
Client-side hooks are <strong>not</strong> copied when the repository is cloned.
However, client-side hooks might be installed in a repository by build steps or by misconfiguration.
</p>
<p>
If hook scripts are allowed on the controller, a client-side hook script installed in a repository on the Jenkins controller will execute when the matching git operation is performed.
For example, if hooks are allowed on the controller and a git repository on the controller includes a <code>post-checkout</code> hook, the hook script will run on the controller after any checkout in that repository.
If hooks are allowed on the controller and a git repository on the controller includes a <code>pre-auto-gc</code> hook, the hook script will run on the controller before any automatic git garbage collection task.
</p>
<p>
See <a href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks">"Customizing Git - Git Hooks"</a> for more details about git repository hooks.
</p>
</div>
Loading