You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@karaf.apache.org by jb...@apache.org on 2022/10/12 09:57:37 UTC
[karaf] branch karaf-4.3.x updated: [KARAF-6321] Add configuration options to protect against CTRl-D/shell:logout exits
This is an automated email from the ASF dual-hosted git repository.
jbonofre pushed a commit to branch karaf-4.3.x
in repository https://gitbox.apache.org/repos/asf/karaf.git
The following commit(s) were added to refs/heads/karaf-4.3.x by this push:
new 966138ca10 [KARAF-6321] Add configuration options to protect against CTRl-D/shell:logout exits
966138ca10 is described below
commit 966138ca10ab125f61a588c2bfeab53a494c254d
Author: Aleksy Wróblewski <al...@bbbit.io>
AuthorDate: Sat Sep 24 23:20:22 2022 +0200
[KARAF-6321] Add configuration options to protect against CTRl-D/shell:logout exits
(cherry picked from commit 7cb44e4e5447440ec99d94a9e281d59dd74dbeec)
---
.../features/standard/src/main/feature/feature.xml | 7 +++
.../apache/karaf/itests/DisabledLogoutTest.java | 54 +++++++++++++++++
manual/src/main/asciidoc/user-guide/console.adoc | 6 +-
manual/src/main/asciidoc/user-guide/remote.adoc | 10 ++++
.../karaf/shell/commands/impl/LogoutAction.java | 12 +++-
.../apache/karaf/shell/api/console/Session.java | 2 +
.../shell/impl/console/ConsoleSessionImpl.java | 70 ++++++++++------------
.../org/apache/karaf/shell/support/ShellUtil.java | 23 +++++++
8 files changed, 141 insertions(+), 43 deletions(-)
diff --git a/assemblies/features/standard/src/main/feature/feature.xml b/assemblies/features/standard/src/main/feature/feature.xml
index 29903a9570..243becf8b0 100644
--- a/assemblies/features/standard/src/main/feature/feature.xml
+++ b/assemblies/features/standard/src/main/feature/feature.xml
@@ -343,6 +343,13 @@ hostKey = ${karaf.etc}/host.key
#
completionMode = GLOBAL
+# If set to true, shell:logout command will not exit Karaf. This can be usfeul to avoid accidental exits.
+# You will be able to exit via 'shutdown' or 'halt' instead.
+disableLogout = false
+# If set to true, it will stop CTRL-D from exiting Karaf. This can be usfeul to avoid accidental exits.
+# You will be able to exit via 'shutdown' or 'halt' instead.
+disableEofExit = false
+
#
# Override allowed SSH cipher algorithms.
# Default: aes256-ctr,aes192-ctr,aes128-ctr
diff --git a/itests/test/src/test/java/org/apache/karaf/itests/DisabledLogoutTest.java b/itests/test/src/test/java/org/apache/karaf/itests/DisabledLogoutTest.java
new file mode 100644
index 0000000000..b6b9241292
--- /dev/null
+++ b/itests/test/src/test/java/org/apache/karaf/itests/DisabledLogoutTest.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.karaf.itests;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.Configuration;
+import org.ops4j.pax.exam.Option;
+import org.ops4j.pax.exam.junit.PaxExam;
+import org.ops4j.pax.exam.karaf.options.KarafDistributionOption;
+import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy;
+import org.ops4j.pax.exam.spi.reactors.PerClass;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+@RunWith(PaxExam.class)
+@ExamReactorStrategy(PerClass.class)
+public class DisabledLogoutTest extends BaseTest {
+
+ @Configuration
+ public Option[] config() {
+ List<Option> options = new ArrayList<>(Arrays.asList(super.config()));
+ options.add(KarafDistributionOption.editConfigurationFilePut("etc/org.apache.karaf.shell.cfg",
+ "disableLogout", "true"));
+ return options.toArray(new Option[options.size()]);
+ }
+
+ @Test
+ public void testShellLogoutDisabled() {
+ executeCommand("shell:logout");
+ // Execute anything at all to verify that we didn't exit from Karaf. If we did, we'd get a runtime exception
+ assertNotNull(executeAlias("ld"));
+ }
+
+}
diff --git a/manual/src/main/asciidoc/user-guide/console.adoc b/manual/src/main/asciidoc/user-guide/console.adoc
index 08fe392ced..25fac30700 100644
--- a/manual/src/main/asciidoc/user-guide/console.adoc
+++ b/manual/src/main/asciidoc/user-guide/console.adoc
@@ -279,7 +279,8 @@ You can create your own aliases in the `etc/shell.init.script` file.
Like on most Unix environments, the Karaf console supports some key bindings:
* the arrows key to navigate in the commands history
-* CTRL-D to logout/shutdown Karaf
+* CTRL-D to logout/shutdown Karaf. Note: to avoid accidental logouts,
+this can be disabled by setting `disableEofExit = true` in `etc/org.apache.karaf.shell.cfg`.
* CTRL-R to search previously executed command
* CTRL-U to remove the current line
@@ -316,7 +317,8 @@ Karaf console provides some core commands similar to a Unix environment:
* `shell:info` prints various information about the current Karaf instance
* `shell:java` executes a Java application
* `shell:less` file pager
-* `shell:logout` disconnects shell from current session
+* `shell:logout` disconnects shell from current session. Note: to avoid accidental logouts,
+this can be disabled by setting `disableLogout = true` in `etc/org.apache.karaf.shell.cfg`.
* `shell:more` is a file pager
* `shell:new` creates a new Java object
* `shell:printf` formats and prints arguments
diff --git a/manual/src/main/asciidoc/user-guide/remote.adoc b/manual/src/main/asciidoc/user-guide/remote.adoc
index 3fb047519a..9e75e8eee8 100644
--- a/manual/src/main/asciidoc/user-guide/remote.adoc
+++ b/manual/src/main/asciidoc/user-guide/remote.adoc
@@ -137,6 +137,13 @@ sftpEnabled=true
# You can change the completion mode directly in the shell console, using shell:completion command.
#
completionMode = GLOBAL
+
+# If set to true, shell:logout command will not exit Karaf. This can be usfeul to avoid accidental exits.
+# You will be able to exit via 'shutdown' or 'halt' instead.
+disableLogout = false
+# If set to true, it will stop CTRL-D from exiting Karaf. This can be usfeul to avoid accidental exits.
+# You will be able to exit via 'shutdown' or 'halt' instead.
+disableEofExit = false
----
The `etc/org.apache.karaf.shell.cfg` configuration file contains different properties to configure the SSHd server:
@@ -376,6 +383,9 @@ When you are connected to a remote Apache Karaf console, you can logout using:
the Apache Karaf instance (as CTRL-D does when used on a local console).
* using `shell:logout` command (or simply `logout`)
+To avoid accidental logouts, one or both of these can be disabled in `etc/org.apache.karaf.shell.cfg`, by setting `disableEofExit = true`
+and `disableLogout = true` respectively.
+
===== Filesystem clients
Apache Karaf SSHd server also provides complete fileystem access via SSH. For security reasons, the available filesystem
diff --git a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
index 365422563d..f4e0260a65 100644
--- a/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
+++ b/shell/commands/src/main/java/org/apache/karaf/shell/commands/impl/LogoutAction.java
@@ -21,6 +21,7 @@ import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.karaf.shell.api.console.Session;
+import org.apache.karaf.shell.support.ShellUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -35,9 +36,14 @@ public class LogoutAction implements Action {
@Override
public Object execute() throws Exception {
- log.info("Disconnecting from current session...");
- session.close();
+ boolean disableLogout = ShellUtil.loadPropertyFromShellCfg("disableLogout", Boolean::parseBoolean, false);
+ if (disableLogout) {
+ log.info("shell:logout disabled in org.apache.karaf.shell.cfg.");
+ } else {
+ log.info("Disconnecting from current session...");
+ session.close();
+ }
+
return null;
}
-
}
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java
index 05930e0faf..ea24aa862f 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/api/console/Session.java
@@ -53,6 +53,8 @@ public interface Session extends Runnable, Closeable {
String IGNORE_INTERRUPTS = "karaf.ignoreInterrupts";
String IS_LOCAL = "karaf.shell.local";
String COMPLETION_MODE = "karaf.completionMode";
+ String DISABLE_EOF_EXIT = "karaf.disableEofExit";
+ String DISABLE_LOGOUT = "karaf.disableLogout";
String COMPLETION_MODE_GLOBAL = "global";
String COMPLETION_MODE_SUBSHELL = "subshell";
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java
index ba7b941864..53af4d2cfe 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/impl/console/ConsoleSessionImpl.java
@@ -19,7 +19,6 @@
package org.apache.karaf.shell.impl.console;
import java.io.File;
-import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
@@ -77,7 +76,6 @@ import static org.apache.felix.gogo.jline.Shell.VAR_SCOPE;
public class ConsoleSessionImpl implements Session {
- private static final String SUPPRESS_WELCOME = "karaf.shell.suppress.welcome";
public static final String SHELL_INIT_SCRIPT = "karaf.shell.init.script";
public static final String SHELL_HISTORY_MAXSIZE = "karaf.shell.history.maxSize";
public static final String SHELL_HISTORY_FILE_MAXSIZE = "karaf.shell.history.file.maxSize";
@@ -85,21 +83,13 @@ public class ConsoleSessionImpl implements Session {
public static final String DEFAULT_PROMPT = "\u001B[1m${USER}\u001B[0m@${APPLICATION}(${SUBSHELL})> ";
public static final String RPROMPT = "RPROMPT";
public static final String DEFAULT_RPROMPT = null;
-
+ private static final String SUPPRESS_WELCOME = "karaf.shell.suppress.welcome";
private static final Logger LOGGER = LoggerFactory.getLogger(ConsoleSessionImpl.class);
-
- // Input stream
- volatile boolean running;
-
- private AtomicBoolean closed = new AtomicBoolean(false);
-
final SessionFactory factory;
final ThreadIO threadIO;
final InputStream in;
final PrintStream out;
final PrintStream err;
- private Runnable closeCallback;
-
final CommandSession session;
final Registry registry;
final Terminal terminal;
@@ -107,7 +97,10 @@ public class ConsoleSessionImpl implements Session {
final History history;
final LineReader reader;
final AggregateMaskingCallback maskingCallback;
-
+ // Input stream
+ volatile boolean running;
+ private AtomicBoolean closed = new AtomicBoolean(false);
+ private Runnable closeCallback;
private Thread thread;
private Properties brandingProps;
@@ -164,7 +157,7 @@ public class ConsoleSessionImpl implements Session {
// Completers
Completer builtinCompleter = createBuiltinCompleter();
CommandsCompleter commandsCompleter = new CommandsCompleter(factory, this);
- Completer completer = (rdr, line, candidates) -> {
+ Completer completer = (rdr, line, candidates) -> {
builtinCompleter.complete(rdr, line, candidates);
commandsCompleter.complete(rdr, line, candidates);
merge(candidates);
@@ -175,13 +168,13 @@ public class ConsoleSessionImpl implements Session {
// Console reader
reader = LineReaderBuilder.builder()
- .terminal(jlineTerminal)
- .appName("karaf")
- .variables(((CommandSessionImpl) session).getVariables())
- .highlighter(new org.apache.felix.gogo.jline.Highlighter(session))
- .parser(new KarafParser(this))
- .completer(completer)
- .build();
+ .terminal(jlineTerminal)
+ .appName("karaf")
+ .variables(((CommandSessionImpl) session).getVariables())
+ .highlighter(new org.apache.felix.gogo.jline.Highlighter(session))
+ .parser(new KarafParser(this))
+ .completer(completer)
+ .build();
// History
final Path file = getHistoryFile();
@@ -224,6 +217,7 @@ public class ConsoleSessionImpl implements Session {
session.put(Session.SCOPE, "shell:bundle:*");
session.put(Session.SUBSHELL, "");
session.put(Session.COMPLETION_MODE, loadCompletionMode());
+ session.put(Session.DISABLE_EOF_EXIT, loadDisableEofExit());
session.put("USER", ShellUtil.getCurrentUserName());
session.put("TERM", terminal.getType());
session.put("APPLICATION", System.getProperty("karaf.name", "root"));
@@ -254,12 +248,14 @@ public class ConsoleSessionImpl implements Session {
public Map<String, List<Completers.CompletionData>> getCompletions() {
return Shell.getCompletions(session);
}
+
@Override
public Set<String> getCommands() {
return factory.getRegistry().getCommands().stream()
.map(c -> c.getScope() + ":" + c.getName())
.collect(Collectors.toSet());
}
+
@Override
public String resolveCommand(String command) {
String resolved = command;
@@ -279,11 +275,13 @@ public class ConsoleSessionImpl implements Session {
}
return resolved;
}
+
@Override
public String commandName(String command) {
int idx = command.indexOf(':');
return idx >= 0 ? command.substring(idx + 1) : command;
}
+
@Override
public Object evaluate(LineReader reader, ParsedLine line, String func) throws Exception {
session.put(Shell.VAR_COMMAND_LINE, line);
@@ -424,7 +422,7 @@ public class ConsoleSessionImpl implements Session {
/**
* On the local console we only show the welcome banner once. This allows to suppress the banner
- * on refreshs of the shell core bundle.
+ * on refreshs of the shell core bundle.
* On ssh we show it every time.
*/
private void welcomeBanner() {
@@ -438,7 +436,7 @@ public class ConsoleSessionImpl implements Session {
}
private boolean isLocal() {
- Boolean isLocal = (Boolean)session.get(Session.IS_LOCAL);
+ Boolean isLocal = (Boolean) session.get(Session.IS_LOCAL);
return isLocal != null && isLocal;
}
@@ -456,7 +454,12 @@ public class ConsoleSessionImpl implements Session {
command = reader.getBuffer().toString();
}
} catch (EndOfFileException e) {
- command = null;
+ boolean disableEofExit = (boolean) session.get(Session.DISABLE_EOF_EXIT);
+ if (disableEofExit) {
+ command = "";
+ } else {
+ command = null;
+ }
} catch (UserInterruptException e) {
command = ""; // Do nothing
} catch (Throwable t) {
@@ -553,21 +556,12 @@ public class ConsoleSessionImpl implements Session {
}
private String loadCompletionMode() {
- String mode;
- try {
- File shellCfg = new File(System.getProperty("karaf.etc"), "/org.apache.karaf.shell.cfg");
- Properties properties = new Properties();
- properties.load(new FileInputStream(shellCfg));
- mode = (String) properties.get("completionMode");
- if (mode == null) {
- LOGGER.debug("completionMode property is not defined in etc/org.apache.karaf.shell.cfg file. Using default completion mode.");
- mode = Session.COMPLETION_MODE_GLOBAL;
- }
- } catch (Exception e) {
- LOGGER.warn("Can't read {}/org.apache.karaf.shell.cfg file. The completion is set to default.", System.getProperty("karaf.etc"));
- mode = Session.COMPLETION_MODE_GLOBAL;
- }
- return mode;
+ return ShellUtil.loadPropertyFromShellCfg("completionMode", java.util.function.Function.identity(),
+ Session.COMPLETION_MODE_GLOBAL);
+ }
+
+ private boolean loadDisableEofExit() {
+ return ShellUtil.loadPropertyFromShellCfg("disableEofExit", Boolean::parseBoolean, false);
}
private void executeScript(String names) {
diff --git a/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java b/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java
index 5f5ef154c4..de871baf02 100644
--- a/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java
+++ b/shell/core/src/main/java/org/apache/karaf/shell/support/ShellUtil.java
@@ -18,13 +18,18 @@
*/
package org.apache.karaf.shell.support;
+import java.io.File;
+import java.io.FileInputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.nio.file.Paths;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
+import java.util.Properties;
+import java.util.function.Function;
import java.util.stream.Collectors;
import javax.security.auth.Subject;
@@ -243,4 +248,22 @@ public class ShellUtil {
s -> s.substring(s.indexOf('=') + 1)));
}
+ public static <T> T loadPropertyFromShellCfg(String key, Function<String, T> parser, T defaultValue) {
+ File shellCfg = Paths.get(System.getProperty("karaf.etc"), "org.apache.karaf.shell.cfg").toFile();
+ try (FileInputStream fis = new FileInputStream(shellCfg)) {
+ Properties properties = new Properties();
+ properties.load(fis);
+
+ String value = (String) properties.get(key);
+ if (value != null) {
+ return parser.apply(value);
+ } else {
+ LOGGER.debug("{} property is not defined in etc/org.apache.karaf.shell.cfg file. Using default value {}.", key, defaultValue);
+ }
+ } catch (Exception e) {
+ LOGGER.warn("Can't read {}/org.apache.karaf.shell.cfg file. The {} is set to default.", key, System.getProperty("karaf.etc"));
+ }
+
+ return defaultValue;
+ }
}