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

Support keyboard-interactive authentication #3

Merged
merged 11 commits into from
Mar 25, 2022
6 changes: 5 additions & 1 deletion docker/DockerFile
Original file line number Diff line number Diff line change
Expand Up @@ -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 && \
Expand All @@ -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

Expand Down
7 changes: 6 additions & 1 deletion docker/build.cmd
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
@echo off
docker build -t sshnet -f DockerFile .

rem Build new image
docker build -t sshnet -f DockerFile .

rem Remove danging images
docker image prune -f
drieseng marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 2 additions & 1 deletion docker/server/script/start.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/bin/ash
/usr/sbin/syslog-ng
/usr/sbin/sshd
# start PAM-enabled ssh daemon as we also want keyboard-interactive authentication to work
/usr/sbin/sshd.pam
tail -f < /var/log/auth.log
42 changes: 25 additions & 17 deletions src/SshNetTests/AuthenticationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
}

Expand All @@ -37,6 +51,8 @@ public void Multifactor_KeyboardInteractiveAndPublicKey()
{
_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive,publickey")
.WithChallengeResponseAuthentication(true)
.WithKeyboardInteractiveAuthentication(true)
.WithUsePAM(true)
.Update()
.Restart();

Expand Down Expand Up @@ -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();

Expand All @@ -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();

Expand All @@ -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();

Expand Down Expand Up @@ -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();

Expand All @@ -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();

Expand All @@ -219,22 +233,24 @@ 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))
{
Assert.AreEqual(0, cmd.ExitStatus, cmd.Error);
}

// 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);
}
}

_remoteSshdConfig.WithAuthenticationMethods(Users.Regular.UserName, "keyboard-interactive")
.WithChallengeResponseAuthentication(true)
.WithKeyboardInteractiveAuthentication(true)
.WithUsePAM(true)
.Update()
.Restart();

Expand Down Expand Up @@ -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();
}
}
}
}
3 changes: 2 additions & 1 deletion src/SshNetTests/Common/RemoteSshdConfigExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
28 changes: 24 additions & 4 deletions src/SshNetTests/RemoteSshd.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -76,12 +76,32 @@ public RemoteSshdConfig(RemoteSshd remoteSshd, IConnectionInfoFactory connection
}
}

public RemoteSshdConfig WithChallengeResponseAuthentication(bool value)
/// <summary>
/// Specifies whether challenge-response authentication is allowed.
/// </summary>
/// <param name="value"><see langword="true"/> to allow challenge-response authentication.</param>
/// <returns>
/// The current <see cref="RemoteSshdConfig"/> instance.
/// </returns>
public RemoteSshdConfig WithChallengeResponseAuthentication(bool? value)
{
_config.ChallengeResponseAuthentication = value;
return this;
}

/// <summary>
/// Specifies whether to allow keyboard-interactive authentication.
/// </summary>
/// <param name="value"><see langword="true"/> to allow keyboard-interactive authentication.</param>
/// <returns>
/// The current <see cref="RemoteSshdConfig"/> instance.
/// </returns>
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));
Expand Down Expand Up @@ -164,7 +184,7 @@ public RemoteSshdConfig WithLogLevel(LogLevel logLevel)

public RemoteSshdConfig WithUsePAM(bool usePAM)
{
_config.UsePAM = true;
_config.UsePAM = usePAM;
return this;
}

Expand Down
2 changes: 1 addition & 1 deletion src/SshNetTests/SshTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ public void Ssh_ShellStream_Exit()
shellStream.ReadLine();
Assert.Fail();
}
catch (NullReferenceException ex)
catch (NullReferenceException)
drieseng marked this conversation as resolved.
Show resolved Hide resolved
{

}
Expand Down