Skip to content

Commit

Permalink
Support keyboard-interactive authentication (#3)
Browse files Browse the repository at this point in the history
Support keyboard-interactive authentication:
* Use PAM-enabled version of OpenSSH.
* Install shadow package to allow us to use the chage command to force password expiry.
* Update Restart() to stop and start the PAM-enabled version of OpenSSH.
* Add method to configure KbdInteractiveAuthentication option.
* Fix path to sftp module.
* Update TearDown to:
** Reset password of sshnet user back to the default password.
** Use change to remove password expiration for the sshnet user.
* Update keyboard-interactive tests to also enable KbdInteractiveAuthentication option.
* Add script to remove dangling images.
* Enable PAM to allow keyboard-interactive authentication to work with a vanilla container.
  • Loading branch information
drieseng authored Mar 25, 2022
1 parent 98972c8 commit 3bfbc71
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 28 deletions.
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
4 changes: 3 additions & 1 deletion docker/build.cmd
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
@echo off
docker build -t sshnet -f DockerFile .

rem Build new image
docker build -t sshnet -f DockerFile .
4 changes: 4 additions & 0 deletions docker/prune.cmd
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
@echo off

rem Remove dangling images
docker image prune -f
2 changes: 1 addition & 1 deletion docker/run.cmd
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
@echo off
docker run --rm -p 22:22 --name sshnet -d sshnet
docker run --rm -p 22:22 --name sshnet -d sshnet
10 changes: 8 additions & 2 deletions docker/server/script/start.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
#!/bin/ash
/usr/sbin/syslog-ng
/usr/sbin/sshd
tail -f < /var/log/auth.log

# 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
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)
{

}
Expand Down

0 comments on commit 3bfbc71

Please sign in to comment.