Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filter out SHA-1 key exchanges and MD5/truncated SHA-1 MACs #35

Merged
merged 4 commits into from
Oct 12, 2020
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
30 changes: 30 additions & 0 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
= SSHD Module

== About

This component provides a built-in SSH server for Jenkins.
It's an alternative interface for the https://www.jenkins.io/doc/book/managing/cli/[Jenkins CLI], and commands can be invoked this way using any SSH client.

NOTE: This is unrelated to https://plugins.jenkins.io/ssh-slaves/[SSH Build Agents]. In that case, the agents are the servers, and the Jenkins controller is the client.

== Configuration

Enable the built-in SSH server in _Manage Jenkins » Configure Global Security_.

=== Advanced Configuration

https://www.jenkins.io/doc/book/managing/system-properties/[System properties] can be used to configure hidden options.
These are generally considered unsupported, i.e. may be removed at any time.

* `org.jenkinsci.main.modules.sshd.SSHD.excludedKeyExchanges` is a comma-separated string of key exchange algorithms to disable.
By default, this disables SHA-1 based algorithms as they're no longer considered safe.
Use an empty string to disable no algorithms.
The names of supported, enabled, and disabled algorithms can be viewed using the https://www.jenkins.io/doc/book/system-administration/viewing-logs/[logger] `org.jenkinsci.main.modules.sshd.SSHD` during initialization on the level `FINE`.
* `org.jenkinsci.main.modules.sshd.SSHD.excludedMacs` is a comma-separated string of HMAC algorithms to disable.
By default, this disables MD5 and truncated SHA-1 based algorithms as they're no longer considered safe.
Use an empty string to disable no algorithms.
The names of supported, enabled, and disabled algorithms can be viewed using the https://www.jenkins.io/doc/book/system-administration/viewing-logs/[logger] `org.jenkinsci.main.modules.sshd.SSHD` during initialization on the level `FINE`.

== Changelog

See link:CHANGELOG.md[CHANGELOG.md].
Copy link
Member

Choose a reason for hiding this comment

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

move to Release Drafter

Copy link
Member Author

Choose a reason for hiding this comment

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

Way out of scope. The changelog file currently exists and GH releases are unused. I'm only adding this file because of #35 (comment)

6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<parent>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>plugin</artifactId>
<version>3.43</version>
<version>4.7</version>
<relativePath />
</parent>

Expand Down Expand Up @@ -31,7 +31,7 @@
<properties>
<revision>2.7</revision>
<changelist>-SNAPSHOT</changelist>
<jenkins.version>2.89.3</jenkins.version>
<jenkins.version>2.249.1</jenkins.version>
<java.level>8</java.level>
</properties>

Expand All @@ -54,7 +54,7 @@
<dependency>
<groupId>org.jenkins-ci.modules</groupId>
<artifactId>ssh-cli-auth</artifactId>
<version>1.5</version>
<version>1.8</version>
</dependency>
</dependencies>

Expand Down
70 changes: 67 additions & 3 deletions src/main/java/org/jenkinsci/main/modules/sshd/SSHD.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import javax.annotation.CheckForSigned;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
Expand All @@ -20,12 +21,16 @@
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.util.ServerTcpPort;
import jenkins.util.SystemProperties;
import jenkins.util.Timer;
import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.apache.sshd.common.NamedFactory;
import org.apache.sshd.common.cipher.BuiltinCiphers;
import org.apache.sshd.common.cipher.Cipher;
import org.apache.sshd.common.kex.KeyExchange;
import org.apache.sshd.common.keyprovider.AbstractKeyPairProvider;
import org.apache.sshd.common.mac.Mac;
import org.apache.sshd.server.SshServer;
import org.apache.sshd.server.auth.UserAuth;
import org.jenkinsci.main.modules.instance_identity.InstanceIdentity;
Expand All @@ -44,7 +49,19 @@ public class SSHD extends GlobalConfiguration {
private static final List<NamedFactory<Cipher>> ENABLED_CIPHERS = Arrays.<NamedFactory<Cipher>>asList(
BuiltinCiphers.aes128ctr, BuiltinCiphers.aes192ctr, BuiltinCiphers.aes256ctr
);


/**
* Comma-separated string of key exchange names to disable. Defaults to a list of DH SHA1 key exchanges, gets its value from {@link SystemProperties}.
*/
private static final String EXCLUDED_KEY_EXCHANGES = SystemProperties.getString(SSHD.class.getName() + ".excludedKeyExchanges",
daniel-beck marked this conversation as resolved.
Show resolved Hide resolved
"diffie-hellman-group-exchange-sha1, diffie-hellman-group14-sha1, diffie-hellman-group1-sha1");

/**
* Comma-separated string of key exchange names to disable. Defaults to a list of MD5 and truncated SHA-1 HMACs, gets its value from {@link SystemProperties}.
*/
private static final String EXCLUDED_MACS = SystemProperties.getString(SSHD.class.getName() + ".excludedMacs",
"hmac-md5, hmac-md5-96, hmac-sha1-96");

@Override
public GlobalConfigurationCategory getCategory() {
return GlobalConfigurationCategory.get(GlobalConfigurationCategory.Security.class);
Expand Down Expand Up @@ -125,7 +142,7 @@ public void run() {
}
return activatedCiphers;
}

public synchronized void start() throws IOException, InterruptedException {
int port = this.port; // Capture local copy to prevent race conditions. Setting port to -1 after the check would blow up later.
if (port<0) return; // don't start it
Expand All @@ -137,6 +154,8 @@ public synchronized void start() throws IOException, InterruptedException {
sshd.setUserAuthFactories(Arrays.<NamedFactory<UserAuth>>asList(new UserAuthNamedFactory()));

sshd.setCipherFactories(getActivatedCiphers());
sshd.setKeyExchangeFactories(filterKeyExchanges(sshd.getKeyExchangeFactories()));
sshd.setMacFactories(filterMacs(sshd.getMacFactories()));
sshd.setPort(port);

sshd.setKeyPairProvider(new AbstractKeyPairProvider() {
Expand All @@ -158,7 +177,52 @@ public Iterable<KeyPair> loadKeys() {
sshd.start();
LOGGER.info("Started SSHD at port " + sshd.getPort());
}


private List<NamedFactory<Mac>> filterMacs(List<NamedFactory<Mac>> macFactories) {
if (StringUtils.isBlank(EXCLUDED_MACS)) {
return macFactories;
}

List<String> excludedNames = Arrays.stream(EXCLUDED_MACS.split(",")).filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toList());

List<NamedFactory<Mac>> filtered = new ArrayList<>();
for (NamedFactory<Mac> macFactory : macFactories) {
final String name = macFactory.getName();
if (excludedNames.contains(name)) {
LOGGER.log(Level.CONFIG, "Excluding " + name);
} else {
LOGGER.log(Level.FINE, "Not excluding " + name);
filtered.add(macFactory);
}
}
return filtered;
}

/**
* Filter key exchanges based on configuration from {@link #EXCLUDED_KEY_EXCHANGES}.
* @param keyExchangeFactories the full list of key exchange factories
* @return a filtered list of key exchange factories
*/
private List<NamedFactory<KeyExchange>> filterKeyExchanges(List<NamedFactory<KeyExchange>> keyExchangeFactories) {
if (StringUtils.isBlank(EXCLUDED_KEY_EXCHANGES)) {
return keyExchangeFactories;
}

List<String> excludedNames = Arrays.stream(EXCLUDED_KEY_EXCHANGES.split(",")).filter(StringUtils::isNotBlank).map(String::trim).collect(Collectors.toList());

List<NamedFactory<KeyExchange>> filtered = new ArrayList<>();
for (NamedFactory<KeyExchange> keyExchangeNamedFactory : keyExchangeFactories) {
final String name = keyExchangeNamedFactory.getName();
if (excludedNames.contains(name)) {
LOGGER.log(Level.CONFIG, "Excluding " + name);
} else {
LOGGER.log(Level.FINE, "Not excluding " + name);
filtered.add(keyExchangeNamedFactory);
}
}
return filtered;
}

public synchronized void restart() {
try {
if (sshd!=null) {
Expand Down