Active Directory Support

This commit is contained in:
Simon Quigley 2024-03-07 15:26:32 -06:00
parent 10acebff46
commit 408ee0338a
9 changed files with 323 additions and 0 deletions

View File

@ -0,0 +1,91 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2024 Simon Quigley <tsimonq2@ubuntu.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include "ActiveDirectoryJob.h"
#include "Config.h"
#include "GlobalStorage.h"
#include "JobQueue.h"
#include "utils/Logger.h"
#include "utils/Permissions.h"
#include "utils/System.h"
#include <QDateTime>
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QTextStream>
#include <QProcess>
ActiveDirectoryJob::ActiveDirectoryJob(QStringList& activeDirectoryInfo)
: Calamares::Job()
, m_activeDirectoryInfo(activeDirectoryInfo)
{
}
QString
ActiveDirectoryJob::prettyName() const
{
return tr( "Enroll system in Active Directory" );
}
QString
ActiveDirectoryJob::prettyDescription() const
{
return tr( "Enroll system in Active Directory" );
}
QString
ActiveDirectoryJob::prettyStatusMessage() const
{
return tr( "Enrolling system in Active Directory" );
}
Calamares::JobResult
ActiveDirectoryJob::exec()
{
QString username = m_activeDirectoryInfo.value(0);
QString password = m_activeDirectoryInfo.value(1);
QString domain = m_activeDirectoryInfo.value(2);
QString ip = m_activeDirectoryInfo.value(3);
Calamares::GlobalStorage* gs = Calamares::JobQueue::instance()->globalStorage();
QString rootMountPoint = gs ? gs->value("rootMountPoint").toString() : QString();
if (!ip.isEmpty()) {
QString hostsFilePath = !rootMountPoint.isEmpty() ? rootMountPoint + "/etc/hosts" : "/etc/hosts";
QFile hostsFile(hostsFilePath);
if (hostsFile.open(QIODevice::Append | QIODevice::Text)) {
QTextStream out(&hostsFile);
out << ip << " " << domain << "\n";
hostsFile.close();
} else {
return Calamares::JobResult::error("Failed to open /etc/hosts for writing.");
}
}
QString installPath = !rootMountPoint.isEmpty() ? rootMountPoint : "/";
QStringList args = {"join", domain, "-U", username, "--install=" + installPath, "--verbose"};
QProcess process;
process.start("realm", args);
process.waitForStarted();
if (!password.isEmpty()) {
process.write((password + "\n").toUtf8());
process.closeWriteChannel();
}
process.waitForFinished(-1);
if (process.exitCode() == 0) {
return Calamares::JobResult::ok();
} else {
QString errorOutput = process.readAllStandardError();
return Calamares::JobResult::error(QString("Failed to join realm: %1").arg(errorOutput));
}
}

View File

@ -0,0 +1,29 @@
/* === This file is part of Calamares - <https://calamares.io> ===
*
* SPDX-FileCopyrightText: 2024 Simon Quigley <tsimonq2@ubuntu.com>
* SPDX-License-Identifier: GPL-3.0-or-later
*
* Calamares is Free Software: see the License-Identifier above.
*
*/
#ifndef ACTIVEDIRECTORYJOB_H
#define ACTIVEDIRECTORYJOB_H
#include "Job.h"
class ActiveDirectoryJob : public Calamares::Job
{
Q_OBJECT
public:
ActiveDirectoryJob( QStringList& activeDirectoryInfo );
QString prettyName() const override;
QString prettyDescription() const override;
QString prettyStatusMessage() const override;
Calamares::JobResult exec() override;
private:
QStringList m_activeDirectoryInfo;
};
#endif /* ACTIVEDIRECTORYJOB_H */

View File

@ -55,6 +55,7 @@ include_directories(${PROJECT_BINARY_DIR}/src/libcalamaresui)
set(_users_src set(_users_src
# Jobs # Jobs
ActiveDirectoryJob.cpp
CreateUserJob.cpp CreateUserJob.cpp
MiscJobs.cpp MiscJobs.cpp
SetPasswordJob.cpp SetPasswordJob.cpp

View File

@ -9,6 +9,7 @@
#include "Config.h" #include "Config.h"
#include "ActiveDirectoryJob.h"
#include "CreateUserJob.h" #include "CreateUserJob.h"
#include "MiscJobs.h" #include "MiscJobs.h"
#include "SetHostNameJob.h" #include "SetHostNameJob.h"
@ -656,6 +657,59 @@ Config::setRootPasswordSecondary( const QString& s )
} }
} }
void
Config::setActiveDirectoryUsed( bool used )
{
m_activeDirectoryUsed = used;
}
bool
Config::getActiveDirectoryEnabled() const
{
return m_activeDirectory;
}
bool
Config::getActiveDirectoryUsed() const
{
return m_activeDirectoryUsed && m_activeDirectory;
}
void
Config::setActiveDirectoryAdminUsername( const QString & s )
{
m_activeDirectoryUsername = s;
}
void
Config::setActiveDirectoryAdminPassword( const QString & s )
{
m_activeDirectoryPassword = s;
}
void
Config::setActiveDirectoryDomain( const QString & s )
{
m_activeDirectoryDomain = s;
}
void
Config::setActiveDirectoryIP( const QString & s )
{
m_activeDirectoryIP = s;
}
QStringList&
Config::getActiveDirectory() const
{
m_activeDirectorySettings.clear();
m_activeDirectorySettings << m_activeDirectoryUsername
<< m_activeDirectoryPassword
<< m_activeDirectoryDomain
<< m_activeDirectoryIP;
return m_activeDirectorySettings;
}
QString QString
Config::rootPassword() const Config::rootPassword() const
{ {
@ -913,6 +967,9 @@ Config::setConfigurationMap( const QVariantMap& configurationMap )
m_sudoStyle = Calamares::getBool( configurationMap, "sudoersConfigureWithGroup", false ) ? SudoStyle::UserAndGroup m_sudoStyle = Calamares::getBool( configurationMap, "sudoersConfigureWithGroup", false ) ? SudoStyle::UserAndGroup
: SudoStyle::UserOnly; : SudoStyle::UserOnly;
// Handle Active Directory enablement
m_activeDirectory = Calamares::getBool( configurationMap, "allowActiveDirectory", false );
// Handle *hostname* key and subkeys and legacy settings // Handle *hostname* key and subkeys and legacy settings
{ {
bool ok = false; // Ignored bool ok = false; // Ignored
@ -990,6 +1047,9 @@ Config::createJobs() const
jobs.append( Calamares::job_ptr( j ) ); jobs.append( Calamares::job_ptr( j ) );
} }
j = new ActiveDirectoryJob( getActiveDirectory() );
jobs.append( Calamares::job_ptr( j ) );
j = new SetupGroupsJob( this ); j = new SetupGroupsJob( this );
jobs.append( Calamares::job_ptr( j ) ); jobs.append( Calamares::job_ptr( j ) );

View File

@ -226,6 +226,12 @@ public:
bool permitWeakPasswords() const { return m_permitWeakPasswords; } bool permitWeakPasswords() const { return m_permitWeakPasswords; }
/// Current setting for "require strong password"? /// Current setting for "require strong password"?
bool requireStrongPasswords() const { return m_requireStrongPasswords; } bool requireStrongPasswords() const { return m_requireStrongPasswords; }
/// Is Active Directory enabled?
bool getActiveDirectoryEnabled() const;
/// Is it both enabled and activated?
bool getActiveDirectoryUsed() const;
/// Config for Active Directory
QStringList& getActiveDirectory() const;
const QList< GroupDescription >& defaultGroups() const { return m_defaultGroups; } const QList< GroupDescription >& defaultGroups() const { return m_defaultGroups; }
/** @brief the names of all the groups for the current user /** @brief the names of all the groups for the current user
@ -292,6 +298,12 @@ public Q_SLOTS:
void setRootPassword( const QString& ); void setRootPassword( const QString& );
void setRootPasswordSecondary( const QString& ); void setRootPasswordSecondary( const QString& );
void setActiveDirectoryUsed( bool used );
void setActiveDirectoryAdminUsername( const QString& );
void setActiveDirectoryAdminPassword( const QString& );
void setActiveDirectoryDomain( const QString& );
void setActiveDirectoryIP( const QString& );
signals: signals:
void userShellChanged( const QString& ); void userShellChanged( const QString& );
void autoLoginGroupChanged( const QString& ); void autoLoginGroupChanged( const QString& );
@ -343,6 +355,14 @@ private:
bool m_isReady = false; ///< Used to reduce readyChanged signals bool m_isReady = false; ///< Used to reduce readyChanged signals
mutable QStringList m_activeDirectorySettings;
bool m_activeDirectory = false;
bool m_activeDirectoryUsed = false;
QString m_activeDirectoryUsername;
QString m_activeDirectoryPassword;
QString m_activeDirectoryDomain;
QString m_activeDirectoryIP;
HostNameAction m_hostnameAction = HostNameAction::EtcHostname; HostNameAction m_hostnameAction = HostNameAction::EtcHostname;
bool m_writeEtcHosts = false; bool m_writeEtcHosts = false;
QString m_hostnameTemplate; QString m_hostnameTemplate;

View File

@ -162,6 +162,34 @@ UsersPage::UsersPage( Config* config, QWidget* parent )
config, &Config::requireStrongPasswordsChanged, ui->checkBoxRequireStrongPassword, &QCheckBox::setChecked ); config, &Config::requireStrongPasswordsChanged, ui->checkBoxRequireStrongPassword, &QCheckBox::setChecked );
} }
// Active Directory is not checked or enabled by default
ui->useADCheckbox->setVisible(m_config->getActiveDirectoryEnabled());
ui->domainLabel->setVisible(false);
ui->domainField->setVisible(false);
ui->domainAdminLabel->setVisible(false);
ui->domainAdminField->setVisible(false);
ui->domainPasswordField->setVisible(false);
ui->domainPasswordLabel->setVisible(false);
ui->ipAddressField->setVisible(false);
ui->ipAddressLabel->setVisible(false);
connect(ui->useADCheckbox, &QCheckBox::toggled, [=](bool checked){
ui->domainLabel->setVisible(checked);
ui->domainField->setVisible(checked);
ui->domainAdminLabel->setVisible(checked);
ui->domainAdminField->setVisible(checked);
ui->domainPasswordField->setVisible(checked);
ui->domainPasswordLabel->setVisible(checked);
ui->ipAddressField->setVisible(checked);
ui->ipAddressLabel->setVisible(checked);
});
connect( ui->domainField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryDomain );
connect( ui->domainAdminField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminUsername );
connect( ui->domainPasswordField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryAdminPassword );
connect( ui->ipAddressField, &QLineEdit::textChanged, config, &Config::setActiveDirectoryIP );
connect( ui->useADCheckbox, &QCheckBox::toggled, config, &Config::setActiveDirectoryUsed );
CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate ); CALAMARES_RETRANSLATE_SLOT( &UsersPage::retranslate );
onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() ); onReuseUserPasswordChanged( m_config->reuseUserPasswordForRoot() );

View File

@ -603,6 +603,93 @@ SPDX-License-Identifier: GPL-3.0-or-later
</item> </item>
</layout> </layout>
</item> </item>
<item>
<spacer name="verticalSpacer_3">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Fixed</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCheckBox" name="useADCheckbox">
<property name="text">
<string>Use Active Directory</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="domainLabel">
<property name="text">
<string>Domain:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domainField"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="domainAdminLabel">
<property name="text">
<string>Domain Administrator:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domainAdminField"/>
</item>
<item>
<widget class="QLabel" name="domainPasswordLabel">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="domainPasswordField">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="ipAddressLabel">
<property name="text">
<string>IP Address (optional):</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="ipAddressField"/>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer_7"> <spacer name="verticalSpacer_7">
<property name="orientation"> <property name="orientation">

View File

@ -265,6 +265,12 @@ hostname:
template: "derp-${cpu}" template: "derp-${cpu}"
forbidden_names: [ localhost ] forbidden_names: [ localhost ]
# Enable Active Directory enrollment support (opt-in)
#
# This uses realmd to enroll the machine in an Active Directory server
# It requires realmd as a runtime dependency of Calamares, if enabled
allowActiveDirectory: false
presets: presets:
fullName: fullName:
# value: "OEM User" # value: "OEM User"

View File

@ -52,6 +52,7 @@ properties:
writeHostsFile: { type: boolean, default: true } writeHostsFile: { type: boolean, default: true }
template: { type: string, default: "${first}-${product}" } template: { type: string, default: "${first}-${product}" }
forbidden_names: { type: array, items: { type: string } } forbidden_names: { type: array, items: { type: string } }
allowActiveDirectory: { type: boolean, default: false }
# Presets # Presets
# #