Skip to content
This repository was archived by the owner on Sep 27, 2024. It is now read-only.

Commit 21d37fd

Browse files
Merge pull request #270 from matejak/remote_with_sudo
Implement possibility to scan by sudoers.
2 parents e2ca2e1 + e8daecc commit 21d37fd

File tree

7 files changed

+151
-26
lines changed

7 files changed

+151
-26
lines changed

doc/user_manual.adoc

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,17 @@ files are not supported yet!
363363
.Selecting a remote machine for scanning
364364
image::scanning_remote_machine.png[align="center"]
365365

366+
The remote user doesn't have to be a superuser - you can setup the remote
367+
`/etc/sudoers` file (using `visudo`) to enable the paswordless sudo for that particular user,
368+
and you check the "user is sudoer" checkbox.
369+
370+
For example, if the scanning user is `oscap-user`, that would involve putting
371+
372+
oscap-user ALL=(root) NOPASSWD: /usr/bin/oscap xccdf eval *
373+
374+
user specification into the `sudoers` file, or into a separate file
375+
that is included by `sudoers` s.a. `/etc/sudoers.d/99-oscap-user`.
376+
366377
=== Enable Online Remediation (optional)
367378

368379
****

include/OscapScannerRemoteSsh.h

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,24 @@ class OscapScannerRemoteSsh : public OscapScannerBase
3131
Q_OBJECT
3232

3333
public:
34-
static void splitTarget(const QString& in, QString& target, unsigned short& port);
34+
static void splitTarget(const QString& in, QString& target, unsigned short& port, bool& userIsSudoer);
3535

3636
OscapScannerRemoteSsh();
3737
virtual ~OscapScannerRemoteSsh();
3838

39+
bool getUserIsSudoer() const;
40+
void setUserIsSudoer(bool userIsSudoer);
3941
virtual void setTarget(const QString& target);
4042
virtual void setSession(ScanningSession* session);
4143

4244
virtual QStringList getCommandLineArgs() const;
4345
virtual void evaluate();
4446

47+
protected:
48+
49+
virtual void selectError(MessageType& kind, const QString& message);
50+
virtual void processError(QString& message);
51+
4552
private:
4653
void ensureConnected();
4754

@@ -57,6 +64,7 @@ class OscapScannerRemoteSsh : public OscapScannerBase
5764
void removeRemoteDirectory(const QString& path, const QString& desc);
5865

5966
SshConnection mSshConnection;
67+
bool mUserIsSudoer;
6068
};
6169

6270
#endif

include/RemoteMachineComboBox.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ class RemoteMachineComboBox : public QWidget
4444

4545
void setRecentMachineCount(unsigned int count);
4646
unsigned int getRecentMachineCount() const;
47+
bool userIsSudoer() const;
4748

4849
public slots:
49-
void notifyTargetUsed(const QString& target);
50+
void notifyTargetUsed(const QString& target, bool userIsSudoer);
5051
void clearHistory();
5152

5253
protected slots:
@@ -65,6 +66,7 @@ class RemoteMachineComboBox : public QWidget
6566

6667
QStringList mRecentTargets;
6768
QComboBox* mRecentComboBox;
69+
QCheckBox* mUserIsSudoer;
6870
};
6971

7072
#endif

src/MainWindow.cpp

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
678678
// In the OscapScannerRemoteSsh class the port will be parsed out again...
679679
const QString target = mUI.localMachineRadioButton->isChecked() ?
680680
"localhost" : mUI.remoteMachineDetails->getTarget();
681+
const bool userIsSudoer = mUI.remoteMachineDetails->userIsSudoer();
681682

682683
bool fetchRemoteResources = mUI.fetchRemoteResourcesCheckbox->isChecked();
683684
try
@@ -689,7 +690,10 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
689690
if (target == "localhost")
690691
mScanner = new OscapScannerLocal();
691692
else
693+
{
692694
mScanner = new OscapScannerRemoteSsh();
695+
((OscapScannerRemoteSsh *)mScanner)->setUserIsSudoer(userIsSudoer);
696+
}
693697

694698
mScanner->setTarget(target);
695699

@@ -759,7 +763,10 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
759763
);
760764

761765
if (target != "localhost")
762-
mUI.remoteMachineDetails->notifyTargetUsed(mScanner->getTarget());
766+
{
767+
bool userIsSudoer = ((OscapScannerRemoteSsh *)mScanner)->getUserIsSudoer();
768+
mUI.remoteMachineDetails->notifyTargetUsed(mScanner->getTarget(), userIsSudoer);
769+
}
763770

764771
mScanThread->start();
765772
}

src/OscapScannerRemoteSsh.cpp

Lines changed: 70 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,16 @@ extern "C"
3737

3838
OscapScannerRemoteSsh::OscapScannerRemoteSsh():
3939
OscapScannerBase(),
40-
mSshConnection(this)
40+
mSshConnection(this),
41+
mUserIsSudoer(false)
4142
{
4243
mSshConnection.setCancelRequestSource(&mCancelRequested);
4344
}
4445

4546
OscapScannerRemoteSsh::~OscapScannerRemoteSsh()
4647
{}
4748

48-
void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsigned short& port)
49+
void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsigned short& port, bool& userIsSudoer)
4950
{
5051
// NB: We dodge a bullet here because the editor will always pass a port
5152
// as the last component. A lot of checking and parsing does not need
@@ -55,10 +56,19 @@ void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsi
5556
// being there and always being the last component.
5657

5758
// FIXME: Ideally, this should split from the right side and stop after one split
58-
QStringList split = in.split(':');
59+
userIsSudoer = false;
60+
QStringList sudoerSplit = in.split(' ');
61+
if (sudoerSplit.size() > 1)
62+
{
63+
if (sudoerSplit.at(1) == "sudo")
64+
{
65+
userIsSudoer = true;
66+
}
67+
}
68+
QStringList hostPortSplit = sudoerSplit.at(0).split(':');
5969

60-
const QString portString = split.back();
61-
split.removeLast();
70+
const QString portString = hostPortSplit.back();
71+
hostPortSplit.removeLast();
6272

6373
{
6474
bool status = false;
@@ -68,25 +78,37 @@ void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsi
6878
port = status ? portCandidate : 22;
6979
}
7080

71-
target = split.join(":");
81+
target = hostPortSplit.join(":");
7282
}
7383

7484
void OscapScannerRemoteSsh::setTarget(const QString& target)
7585
{
76-
OscapScannerBase::setTarget(target);
86+
QStringList sudoerSplit = target.split(' ');
87+
OscapScannerBase::setTarget(sudoerSplit.at(0));
7788

7889
if (mSshConnection.isConnected())
7990
mSshConnection.disconnect();
8091

8192
QString cleanTarget;
8293
unsigned short port;
94+
bool userIsSudoer;
8395

84-
splitTarget(target, cleanTarget, port);
96+
splitTarget(target, cleanTarget, port, userIsSudoer);
8597

8698
mSshConnection.setTarget(cleanTarget);
8799
mSshConnection.setPort(port);
88100
}
89101

102+
bool OscapScannerRemoteSsh::getUserIsSudoer() const
103+
{
104+
return mUserIsSudoer;
105+
}
106+
107+
void OscapScannerRemoteSsh::setUserIsSudoer(bool userIsSudoer)
108+
{
109+
mUserIsSudoer = userIsSudoer;
110+
}
111+
90112
void OscapScannerRemoteSsh::setSession(ScanningSession* session)
91113
{
92114
OscapScannerBase::setSession(session);
@@ -99,6 +121,10 @@ void OscapScannerRemoteSsh::setSession(ScanningSession* session)
99121
QStringList OscapScannerRemoteSsh::getCommandLineArgs() const
100122
{
101123
QStringList args("oscap-ssh");
124+
if (mUserIsSudoer)
125+
{
126+
args.append("--sudo");
127+
}
102128
args.append(mSshConnection.getTarget());
103129
args.append(QString::number(mSshConnection.getPort()));
104130

@@ -235,28 +261,34 @@ void OscapScannerRemoteSsh::evaluate()
235261

236262
if (mScannerMode == SM_OFFLINE_REMEDIATION)
237263
{
238-
args = buildOfflineRemediationArgs(inputFile,
264+
args.append(buildOfflineRemediationArgs(inputFile,
239265
resultFile,
240266
reportFile,
241-
arfFile);
267+
arfFile));
242268
}
243269
else
244270
{
245-
args = buildEvaluationArgs(inputFile,
271+
args.append(buildEvaluationArgs(inputFile,
246272
tailoringFile,
247273
resultFile,
248274
reportFile,
249275
arfFile,
250-
mScannerMode == SM_SCAN_ONLINE_REMEDIATION);
276+
mScannerMode == SM_SCAN_ONLINE_REMEDIATION));
251277
}
252278

253279
const QString sshCmd = args.join(" ");
254280

255281
emit infoMessage(QObject::tr("Starting the remote process..."));
256282

257283
QProcess process(this);
284+
QString sudo;
285+
if (mUserIsSudoer)
286+
{
287+
// tell sudo not to bother to read password from the terminal
288+
sudo = " sudo -n";
289+
}
258290

259-
process.start(SCAP_WORKBENCH_LOCAL_SSH_PATH, baseArgs + QStringList(QString("cd '%1'; " SCAP_WORKBENCH_REMOTE_OSCAP_PATH " %2").arg(workingDir).arg(sshCmd)));
291+
process.start(SCAP_WORKBENCH_LOCAL_SSH_PATH, baseArgs + QStringList(QString("cd '%1';" "%2 " SCAP_WORKBENCH_REMOTE_OSCAP_PATH " %3").arg(workingDir).arg(sudo).arg(sshCmd)));
260292
process.waitForStarted();
261293

262294
if (process.state() != QProcess::Running)
@@ -328,6 +360,31 @@ void OscapScannerRemoteSsh::evaluate()
328360
signalCompletion(mCancelRequested);
329361
}
330362

363+
void OscapScannerRemoteSsh::selectError(MessageType& kind, const QString& message)
364+
{
365+
OscapScannerBase::selectError(kind, message);
366+
if (mUserIsSudoer)
367+
{
368+
if (message.contains(QRegExp("^sudo:")))
369+
{
370+
kind = MSG_ERROR;
371+
}
372+
}
373+
374+
}
375+
376+
void OscapScannerRemoteSsh::processError(QString& message)
377+
{
378+
OscapScannerBase::processError(message);
379+
if (mUserIsSudoer && message.contains(QRegExp("^sudo:")))
380+
{
381+
message.replace(QRegExp("^sudo:"), "Error invoking sudo on the host:");
382+
message += ".\nOnly passwordless sudo setup on the remote host is supported by scap-workbench.";
383+
message += " \nTo configure a non-privileged user oscap-user to run only the oscap binary as root, "
384+
"add this User Specification to your sudoers file: oscap-user ALL=(root) NOPASSWD: /usr/bin/oscap xccdf eval *";
385+
}
386+
}
387+
331388
void OscapScannerRemoteSsh::ensureConnected()
332389
{
333390
if (mSshConnection.isConnected())

src/RemoteMachineComboBox.cpp

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ RemoteMachineComboBox::RemoteMachineComboBox(QWidget* parent):
3030

3131
#if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0))
3232
// placeholder text is only supported in Qt 4.7 onwards
33-
mUI.host->setPlaceholderText(QObject::tr("username@hostname"));
33+
mUI.host->setPlaceholderText(QObject::tr("username@hostname [sudo]"));
3434
#endif
3535

3636
mQSettings = new QSettings(this);
@@ -41,6 +41,8 @@ RemoteMachineComboBox::RemoteMachineComboBox(QWidget* parent):
4141
this, SLOT(updateHostPort(int))
4242
);
4343

44+
mUserIsSudoer = mUI.userIsSudoer;
45+
4446
setRecentMachineCount(5);
4547
syncFromQSettings();
4648

@@ -51,6 +53,11 @@ RemoteMachineComboBox::~RemoteMachineComboBox()
5153
delete mQSettings;
5254
}
5355

56+
bool RemoteMachineComboBox::userIsSudoer() const
57+
{
58+
return mUserIsSudoer->isChecked();
59+
}
60+
5461
QString RemoteMachineComboBox::getTarget() const
5562
{
5663
return QString("%1:%2").arg(mUI.host->text()).arg(mUI.port->value());
@@ -70,11 +77,12 @@ unsigned int RemoteMachineComboBox::getRecentMachineCount() const
7077
return mRecentTargets.size();
7178
}
7279

73-
void RemoteMachineComboBox::notifyTargetUsed(const QString& target)
80+
void RemoteMachineComboBox::notifyTargetUsed(const QString& target, bool userIsSudoer)
7481
{
7582
QString host;
7683
unsigned short port;
77-
OscapScannerRemoteSsh::splitTarget(target, host, port);
84+
bool placeholder;
85+
OscapScannerRemoteSsh::splitTarget(target, host, port, placeholder);
7886

7987
// skip invalid suggestions
8088
if (host.isEmpty() || port == 0)
@@ -83,7 +91,8 @@ void RemoteMachineComboBox::notifyTargetUsed(const QString& target)
8391
const unsigned int machineCount = getRecentMachineCount();
8492

8593
// this moves target to the beginning of the list if it was in the list already
86-
mRecentTargets.prepend(target);
94+
QString targetWithSudo = target + (userIsSudoer ? " sudo" : "");
95+
mRecentTargets.prepend(targetWithSudo);
8796
mRecentTargets.removeDuplicates();
8897

8998
setRecentMachineCount(machineCount);
@@ -99,6 +108,7 @@ void RemoteMachineComboBox::clearHistory()
99108
{
100109
mUI.host->setText("");
101110
mUI.port->setValue(22);
111+
mUI.userIsSudoer->setChecked(false);
102112

103113
const unsigned int machineCount = getRecentMachineCount();
104114
mRecentTargets.clear();
@@ -160,6 +170,7 @@ void RemoteMachineComboBox::updateHostPort(int index)
160170
{
161171
mUI.host->setText("");
162172
mUI.port->setValue(22);
173+
mUI.userIsSudoer->setChecked(false);
163174
return;
164175
}
165176

@@ -172,10 +183,11 @@ void RemoteMachineComboBox::updateHostPort(int index)
172183

173184
QString host;
174185
unsigned short port;
186+
bool userIsSudoer;
175187

176-
OscapScannerRemoteSsh::splitTarget(target, host, port);
188+
OscapScannerRemoteSsh::splitTarget(target, host, port, userIsSudoer);
177189

178190
mUI.host->setText(host);
179191
mUI.port->setValue(port);
180-
192+
mUI.userIsSudoer->setChecked(userIsSudoer);
181193
}

0 commit comments

Comments
 (0)