diff --git a/docker/DockerFile b/docker/DockerFile
index 778f294..9c8f0cd 100644
--- a/docker/DockerFile
+++ b/docker/DockerFile
@@ -7,6 +7,8 @@ COPY --chown=sshnet:sshnet user/sshnet /home/sshnet/.ssh
RUN apk add --no-cache syslog-ng && \
# install and configure sshd
apk add --no-cache openssh && \
+ # install openssh-server-pam to allow for keyboard-interactive authentication
+ apk add --no-cache openssh-server-pam && \
chmod 400 /etc/ssh/ssh*key && \
sed -i 's/#PasswordAuthentication yes/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
sed -i 's/#LogLevel\s*INFO/LogLevel DEBUG3/' /etc/ssh/sshd_config && \
@@ -29,7 +31,9 @@ RUN apk add --no-cache syslog-ng && \
passwd -u sshnetadm && \
echo 'sshnetadm:ssh4ever' | chpasswd && \
addgroup sshnetadm sudo && \
- dos2unix /opt/sshnet/*
+ dos2unix /opt/sshnet/* && \
+ # install shadow package; we use chage command in this package to expire/unexpire password of the sshnet user
+ apk add --no-cache shadow
EXPOSE 22 22
diff --git a/docker/build.cmd b/docker/build.cmd
index b0b5836..9b823df 100644
--- a/docker/build.cmd
+++ b/docker/build.cmd
@@ -1,2 +1,4 @@
@echo off
-docker build -t sshnet -f DockerFile .
\ No newline at end of file
+
+rem Build new image
+docker build -t sshnet -f DockerFile .
diff --git a/docker/prune.cmd b/docker/prune.cmd
new file mode 100644
index 0000000..262d66c
--- /dev/null
+++ b/docker/prune.cmd
@@ -0,0 +1,4 @@
+@echo off
+
+rem Remove dangling images
+docker image prune -f
diff --git a/docker/run.cmd b/docker/run.cmd
index 398c267..299e7a4 100644
--- a/docker/run.cmd
+++ b/docker/run.cmd
@@ -1,2 +1,2 @@
@echo off
-docker run --rm -p 22:22 --name sshnet -d sshnet
\ No newline at end of file
+docker run --rm -p 22:22 --name sshnet -d sshnet
diff --git a/docker/server/script/start.sh b/docker/server/script/start.sh
index baa6464..e7e9f75 100644
--- a/docker/server/script/start.sh
+++ b/docker/server/script/start.sh
@@ -1,4 +1,10 @@
#!/bin/ash
/usr/sbin/syslog-ng
-/usr/sbin/sshd
-tail -f < /var/log/auth.log
\ No newline at end of file
+
+# allow us to make changes to /etc/hosts; we need this for the port forwarding tests
+chmod 777 /etc/hosts
+
+# start PAM-enabled ssh daemon as we also want keyboard-interactive authentication to work
+/usr/sbin/sshd.pam
+
+tail -f < /var/log/auth.log
diff --git a/src/SshNetTests/AuthenticationTests.cs b/src/SshNetTests/AuthenticationTests.cs
index 4978e32..5dae02b 100644
--- a/src/SshNetTests/AuthenticationTests.cs
+++ b/src/SshNetTests/AuthenticationTests.cs
@@ -26,9 +26,23 @@ public void SetUp()
[TestCleanup]
public void TearDown()
{
- if (_remoteSshdConfig != null)
+ _remoteSshdConfig?.Reset();
+
+ using (var client = new SshClient(_adminConnectionInfoFactory.Create()))
{
- _remoteSshdConfig.Reset();
+ client.Connect();
+
+ // Reset the password back to the "regular" password.
+ using (var cmd = client.RunCommand($"echo \"{Users.Regular.Password}\n{Users.Regular.Password}\" | sudo passwd " + Users.Regular.UserName))
+ {
+ Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+ }
+
+ // Remove password expiration
+ using (var cmd = client.RunCommand($"sudo chage --expiredate -1 " + Users.Regular.UserName))
+ {
+ Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
+ }
}
}
@@ -37,6 +51,8 @@ public void Multifactor_KeyboardInteractiveAndPublicKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey")
.WithChallengeResponseAuthentication(true)
+ .WithKeyboardInteractiveAuthentication(true)
+ .WithUsePAM(true)
.Update()
.Restart();
@@ -95,7 +111,9 @@ public void Multifactor_Password_MatchPartialSuccessLimit()
public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,keyboard-interactive")
- .WithChallengeResponseAuthentication(false)
+ .WithChallengeResponseAuthentication(true)
+ .WithKeyboardInteractiveAuthentication(true)
+ .WithUsePAM(true)
.Update()
.Restart();
@@ -111,7 +129,6 @@ public void Multifactor_Password_Or_PublicKeyAndKeyboardInteractive()
public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password publickey,password")
- .WithChallengeResponseAuthentication(false)
.Update()
.Restart();
@@ -136,7 +153,6 @@ public void Multifactor_Password_Or_PublicKeyAndPassword_BadPassword()
public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,publickey password,password")
- .WithChallengeResponseAuthentication(false)
.Update()
.Restart();
@@ -169,7 +185,6 @@ public void Multifactor_PasswordAndPublicKey_Or_PasswordAndPassword()
public void Multifactor_PasswordAndPassword_Or_PublicKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password,password publickey")
- .WithChallengeResponseAuthentication(false)
.Update()
.Restart();
@@ -192,7 +207,6 @@ public void Multifactor_PasswordAndPassword_Or_PublicKey()
public void Multifactor_Password_Or_Password()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "password password")
- .WithChallengeResponseAuthentication(false)
.Update()
.Restart();
@@ -219,7 +233,7 @@ public void KeyboardInteractive_PasswordExpired()
{
client.Connect();
- // Temporarity modify password so that when a expire this password, we change reset the password back to
+ // Temporarity modify password so that when we expire this password, we change reset the password back to
// the "regular" password.
using (var cmd = client.RunCommand($"echo \"{temporaryPassword}\n{temporaryPassword}\" | sudo passwd " + Users.Regular.UserName))
{
@@ -227,7 +241,7 @@ public void KeyboardInteractive_PasswordExpired()
}
// Force the password to expire immediately
- using (var cmd = client.RunCommand($"sudo passwd --expire " + Users.Regular.UserName))
+ using (var cmd = client.RunCommand($"sudo chage -d 0 " + Users.Regular.UserName))
{
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}
@@ -235,6 +249,8 @@ public void KeyboardInteractive_PasswordExpired()
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
.WithChallengeResponseAuthentication(true)
+ .WithKeyboardInteractiveAuthentication(true)
+ .WithUsePAM(true)
.Update()
.Restart();
@@ -274,14 +290,6 @@ public void KeyboardInteractive_PasswordExpired()
client.Connect();
Assert.AreEqual(4, authenticationPromptCount);
}
-
- _remoteSshdConfig.Reset();
-
- connectionInfo = _connectionInfoFactory.Create(_authenticationMethodFactory.CreateRegulatUserPasswordAuthenticationMethod());
- using (var client = new SftpClient(connectionInfo))
- {
- client.Connect();
- }
}
}
}
diff --git a/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs b/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs
index c058d8b..9c66efb 100644
--- a/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs
+++ b/src/SshNetTests/Common/RemoteSshdConfigExtensions.cs
@@ -10,11 +10,12 @@ public static void Reset(this RemoteSshdConfig remoteSshdConfig)
{
remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, DefaultAuthenticationMethods)
.WithChallengeResponseAuthentication(false)
+ .WithKeyboardInteractiveAuthentication(false)
.WithLogLevel(LogLevel.Debug3)
.ClearHostKeyFiles()
.AddHostKeyFile(HostKeyFile.Rsa.FilePath)
.ClearSubsystems()
- .AddSubsystem(new Subsystem("sftp", "/usr/lib/openssh/sftp-server"))
+ .AddSubsystem(new Subsystem("sftp", "/usr/lib/ssh/sftp-server"))
.ClearCiphers()
.ClearKeyExchangeAlgorithms()
.ClearHostKeyAlgorithms()
diff --git a/src/SshNetTests/RemoteSshd.cs b/src/SshNetTests/RemoteSshd.cs
index 1b5904a..7652f65 100644
--- a/src/SshNetTests/RemoteSshd.cs
+++ b/src/SshNetTests/RemoteSshd.cs
@@ -29,14 +29,14 @@ public RemoteSshd Restart()
client.Connect();
// Kill all processes that start with 'sshd' and that run as root
- var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd");
+ var stopCommand = client.CreateCommand("sudo pkill -9 -U 0 sshd.pam");
var stopOutput = stopCommand.Execute();
if (stopCommand.ExitStatus != 0)
{
throw new ApplicationException($"Stopping ssh service failed with exit code {stopCommand.ExitStatus}.\r\n{stopOutput}");
}
- var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd");
+ var resetFailedCommand = client.CreateCommand("sudo /usr/sbin/sshd.pam");
var resetFailedOutput = resetFailedCommand.Execute();
if (resetFailedCommand.ExitStatus != 0)
{
@@ -76,12 +76,32 @@ public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connection
}
}
- public RemoteSshdConfig WithChallengeResponseAuthentication(bool value)
+ ///
+ /// Specifies whether challenge-response authentication is allowed.
+ ///
+ /// to allow challenge-response authentication.
+ ///
+ /// The current instance.
+ ///
+ public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value)
{
_config.ChallengeResponseAuthentication = value;
return this;
}
+ ///
+ /// Specifies whether to allow keyboard-interactive authentication.
+ ///
+ /// to allow keyboard-interactive authentication.
+ ///
+ /// The current instance.
+ ///
+ public RemoteSshdConfig WithKeyboardInteractiveAuthentication(bool value)
+ {
+ _config.KeyboardInteractiveAuthentication = value;
+ return this;
+ }
+
public RemoteSshdConfig WithAuthenticationMethods(string user, string authenticationMethods)
{
var sshNetMatch = _config.Matches.FirstOrDefault(m => m.Users.Contains(user));
@@ -164,7 +184,7 @@ public RemoteSshdConfig WithLogLevel(LogLevel logLevel)
public RemoteSshdConfig WithUsePAM(bool usePAM)
{
- _config.UsePAM = true;
+ _config.UsePAM = usePAM;
return this;
}
diff --git a/src/SshNetTests/SshTests.cs b/src/SshNetTests/SshTests.cs
index d9b7974..c3af625 100644
--- a/src/SshNetTests/SshTests.cs
+++ b/src/SshNetTests/SshTests.cs
@@ -63,7 +63,7 @@ public void Ssh_ShellStream_Exit()
shellStream.ReadLine();
Assert.Fail();
}
- catch (NullReferenceException ex)
+ catch (NullReferenceException)
{
}