Skip to content

Commit

Permalink
master keycloak:449 add keycloak as IdP for doil managed ILIAS Instances
Browse files Browse the repository at this point in the history
* add keycloak commands (down/login/restart/up)
* add saml states (enable-saml/disable-saml)
* add keycloak state
* add keycloak template
* update CHANGELOG
* update README
* add an update script
* tested so far on my local host
* adjust tests
  • Loading branch information
daniwe4 committed Nov 13, 2024
1 parent 4f697a2 commit 67486a8
Show file tree
Hide file tree
Showing 69 changed files with 2,221 additions and 44 deletions.
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,45 @@
# Changelog

## 20241113
## What's Changed
* add keycloak support

## 20241104
## What's Changed
* update salt urls

## 20241010
## What's Changed
* execute proxy command only if instance is up
* add 'Options -Indexes' to default site

## 20240930
## What's Changed
* add captainhook states
* longer sleep between 'docker commit' and 'settinsg premissions'
* use correct pathes during import command

## 20240926
## What's Changed
* php: add version 8.3

## 20240902
## What's Changed
* add support for ilias 10 exports
* also export branches with no matching doil repo

## 20240807
## What's Changed
* change path for minion_master.pub

## 20240806
## What's Changed
* move nodejs step before composer step and change execution dir in nodejs state

## 20240801
## What's Changed
* fix problems with the ILIAS database password

## 20240628
## What's Changed
* use Salt Repos for salt-master and salt-minion
Expand Down
6 changes: 4 additions & 2 deletions CI/validate-sls-files.sh
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@ do
TMPFILE="${FILE}.tmp"
touch $TMPFILE

echo "---" > ${TMPFILE}
echo "---" > ${TMPFILE}
sed 's/{%.*%}//' ${FILE} >> ${TMPFILE}
sed 's/{{.*}}/bar/' ${TMPFILE} > ${TMPFILE}.tmp
sed '/%[A-Z].*%/d' ${TMPFILE} >> ${TMPFILE}.1
sed 's/{{.*}}/bar/' ${TMPFILE}.1 > ${TMPFILE}.tmp
rm ${TMPFILE}.1
mv ${TMPFILE}.tmp ${TMPFILE}

TEST=$(yamllint -c ./CI/sls-lint-rules.yml ${FILE}.tmp)
Expand Down
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,8 +308,8 @@ users, so make sure to understand what you are doing.

* `doil salt:login` logs the user into the main salt server
* `doil salt:prune` prunes the main salt server
* `doil salt:start` starts the salt main server
* `doil salt:stop` stops the salt main server
* `doil salt:up` starts the salt main server
* `doil salt:down` stops the salt main server
* `doil salt:restart` restarts the salt main server
* `doil salt:states` to list the available states

Expand All @@ -324,8 +324,8 @@ users, so make sure to understand what you are doing.

* `doil proxy:login` logs the user into the proxy server
* `doil proxy:prune` removes the configuration of the proxy server
* `doil proxy:start` starts the proxy server
* `doil proxy:stop` stops the proxy server
* `doil proxy:up` starts the proxy server
* `doil proxy:down` stops the proxy server
* `doil proxy:restart` restarts the proxy server
* `doil proxy:reload` reloads the configuration

Expand Down Expand Up @@ -372,10 +372,48 @@ users, so make sure to understand what you are doing.

* `doil mail:change-password` changes the default password for roundcube
* `doil mail:login` logs the user into the mail server
* `doil mail:start` starts the mail server
* `doil mail:stop` stops the mail server
* `doil mail:up` starts the mail server
* `doil mail:down` stops the mail server
* `doil mail:restart` restarts the mail server

### Keycloak Server

The Keycloak server is an identity provider that allows you to log in to all
ILIAS instances managed by **doil** with one password.
This requires some settings in the doil.conf file. 'doil.conf' can be found
under setup/conf/doil.conf. The adjustments must be made before an update/install.

The following settings are available:

* `enable_keycloak=[true/false]` decides whether keycloak is installed during
an update/install [default:false]
* `keycloak_hostname=http://doil/keycloak` keycloak url, please pay attention to https/http
* `keycloak_new_admin_password=12345` admin password
* `keycloak_old_admin_password=12345` If the password is changed during an update, the old
password must be entered here. Please make sure to adjust it after the update.
* `keycloak_db_username=admin` database user name
* `keycloak_db_password=admin` database user password

If you use keycloak, the salt state enable-saml must be called for existing ILIAS instances.
This is done using the 'doil apply <instance_name>' command.
Newly created instances check whether keycloak is enabled and set up the instance directly.

The corresponding users must be created in Keycloak and ILIAS.
In ILIAS it is important that the 'External Account' field in the user administration receives
the same email that is also set in Keycloak.

To be able to dive deeper into the inner workings of **doil** or customize it
to fit your workflow or requirements, **doil** provides commands to tamper with
the keycloak in the background. These commands will not be required by ordinary
users, so make sure to understand what you are doing.

* `doil keycloak:login` logs the user into the keycloak server
* `doil keycloak:up` starts the keycloak server
* `doil keycloak:down` stops the keycloak server
* `doil keycloak:restart` restarts the keycloak server

See `doil keycloak:<command> --help` for more information

### xdedug

**doil** provides two options to enable xdebug for the given instance.
Expand Down
2 changes: 1 addition & 1 deletion app/src/App.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

class App extends Application
{
const NAME = "Doil Version 20241010 - build 2024-10-10";
const NAME = "Doil Version 20241113 - build 2024-11-13";

public function __construct(Command ...$commands)
{
Expand Down
3 changes: 2 additions & 1 deletion app/src/Commands/Instances/ApplyCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ class ApplyCommand extends Command
"reactor",
"change-roundcube-password",
"nodejs",
"proxy-enable-https"
"proxy-enable-https",
"keycloak"
];

protected static $defaultName = "instances:apply";
Expand Down
45 changes: 42 additions & 3 deletions app/src/Commands/Instances/CreateCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,18 +31,20 @@ class CreateCommand extends Command
protected const GLOBAL_REPO_PATH = "/usr/local/share/doil/repositories";
protected const LOCAL_INSTANCES_PATH = "/.doil/instances";
protected const GLOBAL_INSTANCES_PATH = "/usr/local/share/doil/instances";
protected const KEYCLOAK_PATH = "/usr/local/lib/doil/server/keycloak";
protected const BASIC_FOLDERS = [
"/conf",
"/conf/salt",
"/volumes/db",
"/volumes/index",
"/volumes/data",
"/volumes/cert",
"/volumes/logs/error",
"/volumes/logs/apache",
"/volumes/etc/apache2",
"/volumes/logs/xdebug",
"/volumes/etc/php",
"/volumes/etc/mysql",
"/volumes/etc/mysql"
];

protected static $defaultName = "instances:create";
Expand Down Expand Up @@ -103,12 +105,14 @@ public function execute(InputInterface $input, OutputInterface $output) : int
{
$options = $this->gatherOptionData($input, $output);

$host = explode("=", $this->filesystem->getLineInFile("/etc/doil/doil.conf", "host"))[1];
$instance_path = $options["target"] . "/" . $options["name"];
$suffix = $options["global"] ? "global" : "local";
$instance_name = $options["name"] . "_" . $suffix;
$instance_salt_name = $options["name"] . "." . $suffix;
$user_name = $this->posix->getCurrentUserName();
$home_dir = $this->posix->getHomeDirectory($this->posix->getUserId());
$keycloak = false;

if ($this->filesystem->exists($instance_path)) {
$this->writer->error(
Expand All @@ -127,6 +131,10 @@ public function execute(InputInterface $input, OutputInterface $output) : int
return Command::FAILURE;
}

if ($this->filesystem->exists(self::KEYCLOAK_PATH)) {
$keycloak = true;
}

$this->writer->beginBlock($output, "Creating instance " . $options['name']);

if (isset($options["repo_path"]) && ! $this->filesystem->exists($options["repo_path"])) {
Expand Down Expand Up @@ -254,13 +262,19 @@ public function execute(InputInterface $input, OutputInterface $output) : int
"%TPL_PROJECT_DOMAINNAME%",
$suffix
);
$this->filesystem->replaceStringInFile(
$instance_path . "/docker-compose.yml",
"%TPL_HOST_DOMAIN%",
$host
);
$this->writer->endBlock();

// building minion image
$this->writer->beginBlock($output, "Building minion image");
$this->docker->runContainer($instance_name);
$usr_id = (string) $this->posix->getUserId();
$group_id = (string) $this->posix->getGroupId();
$this->docker->executeDockerCommand($instance_name, "mkdir -p /var/ilias/cert");
$this->docker->executeDockerCommand($instance_name, "usermod -u $usr_id www-data");
$this->docker->executeDockerCommand($instance_name, "groupmod -g $group_id www-data");
$this->docker->executeDockerCommand($instance_name, "/etc/init.d/mariadb start");
Expand All @@ -286,22 +300,33 @@ public function execute(InputInterface $input, OutputInterface $output) : int
// set grains
$this->writer->beginBlock($output, "Setting up instance configuration");
$mysql_password = $this->generatePassword(16);

$cron_password = "not-needed";
if ($ilias_version < 9) {
$cron_password = $this->generatePassword(16);
}
$host = explode("=", $this->filesystem->getLineInFile("/etc/doil/doil.conf", "host"));

if ($keycloak) {
$samlpass = $this->generatePassword(33);
$this->docker->setGrain($instance_salt_name, "samlpass", "$samlpass");
sleep(1);
$samlsalt = $this->generatePassword(33);
$this->docker->setGrain($instance_salt_name, "samlsalt", "$samlsalt");
sleep(1);
}

$this->docker->setGrain($instance_salt_name, "mpass", "$mysql_password");
sleep(1);
$this->docker->setGrain($instance_salt_name, "cpass", "$cron_password");
sleep(1);
$this->docker->setGrain($instance_salt_name, "doil_domain", "http://" . $host[1] . "/" . $options["name"]);
$this->docker->setGrain($instance_salt_name, "doil_domain", "http://" . $host . "/" . $options["name"]);
sleep(1);
$this->docker->setGrain($instance_salt_name, "doil_project_name", $options["name"]);
sleep(1);
$this->docker->setGrain($instance_salt_name, "doil_host_system", "linux");
sleep(1);
$this->docker->setGrain($instance_salt_name, "ilias_version", $ilias_version);
sleep(1);
$this->docker->executeDockerCommand("doil_saltmain", "salt \"" . $instance_salt_name . "\" saltutil.refresh_grains");
$this->writer->endBlock();

Expand Down Expand Up @@ -378,6 +403,20 @@ public function execute(InputInterface $input, OutputInterface $output) : int
$this->docker->applyState($instance_salt_name, "enable-captainhook");
$this->writer->endBlock();

if ($ilias_version >= 8.0) {
// apply prevent_super_global_replacement state
$this->writer->beginBlock($output, "Apply prevent_super_global_replacement state");
$this->docker->applyState($instance_salt_name, "prevent-super-global-replacement");
$this->writer->endBlock();
}

if ($keycloak) {
// apply enable-saml state
$this->writer->beginBlock($output, "Apply enable-saml state");
$this->docker->applyState($instance_salt_name, "enable-saml");
$this->writer->endBlock();
}

// apply access state
$this->writer->beginBlock($output, "Apply access state");
$this->docker->applyState($instance_salt_name, "access");
Expand Down
12 changes: 10 additions & 2 deletions app/src/Commands/Instances/DeleteCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ class DeleteCommand extends Command
{
protected const SALT_MAIN = "/usr/local/lib/doil/server/salt/";
protected const POSTFIX = "/usr/local/lib/doil/server/mail/";
protected const PROXY_PATH = "/usr/local/lib/doil/server/proxy/";
protected const KEYCLOAK_PATH = "/usr/local/lib/doil/server/keycloak";

protected static $defaultName = "instances:delete";
protected static $defaultDescription =
Expand Down Expand Up @@ -132,20 +132,28 @@ protected function deleteInstance(
) : int {
$this->writer->beginBlock($output, "Delete instance $instance");

$is_up = $this->docker->isInstanceUp($path);
$instance_dir = $this->filesystem->readLink($path);
$this->filesystem->remove($path);
$this->filesystem->remove($instance_dir);

$this->docker->removeContainer($instance . "_" . $suffix);

$this->docker->executeCommand(self::SALT_MAIN, "doil_saltmain", "salt-key", "-d", "$instance.$suffix", "-y", "-q");
if ($this->docker->isInstanceUp($path)) {
if ($is_up) {
$this->docker->executeDockerCommand(
"doil_proxy",
"rm -f /etc/nginx/conf.d/sites/$instance.conf && /root/generate_index_html.sh"
);
}

if ($this->filesystem->exists(self::KEYCLOAK_PATH)) {
$this->docker->executeDockerCommand(
"doil_keycloak",
"/root/delete_keycloak_client.sh $instance"
);
}

if ($this->docker->hasVolume($instance)) {
$this->docker->removeVolume($instance);
}
Expand Down
1 change: 1 addition & 0 deletions app/src/Commands/Instances/StatusCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public function execute(InputInterface $input, OutputInterface $output) : int
strstr($a, "doil_mail") ||
strstr($a, "doil_proxy") ||
strstr($a, "doil_saltmain") ||
strstr($a, "doil_keycloak") ||
strstr($a, "_local") ||
strstr($a, "_global")
) {
Expand Down
54 changes: 54 additions & 0 deletions app/src/Commands/Keycloak/DownCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php declare(strict_types=1);

/* Copyright (c) 2022 - Daniel Weise <[email protected]> - Extended GPL, see LICENSE */

namespace CaT\Doil\Commands\Keycloak;

use CaT\Doil\Lib\Docker\Docker;
use CaT\Doil\Lib\ConsoleOutput\Writer;
use CaT\Doil\Lib\FileSystem\Filesystem;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class DownCommand extends Command
{
protected const KEYCLOAK_PATH = "/usr/local/lib/doil/server/keycloak";

protected static $defaultName = "keycloak:down";
protected static $defaultDescription = "Stops the keycloak server";

protected Docker $docker;
protected Writer $writer;
protected Filesystem $filesystem;

public function __construct(Docker $docker, Writer $writer, Filesystem $filesystem)
{
$this->docker = $docker;
$this->writer = $writer;
$this->filesystem = $filesystem;

parent::__construct();
}

protected function configure() : void
{
if (!$this->filesystem->exists(self::KEYCLOAK_PATH)) {
$this->setHidden(true);
}
}

public function execute(InputInterface $input, OutputInterface $output) : int
{
if (! $this->docker->isInstanceUp(self::KEYCLOAK_PATH)) {
$output->writeln("Nothing to do. Keycloak is already down.");
return Command::SUCCESS;
}

$this->writer->beginBlock($output, "Stop keycloak");
$this->docker->stopContainerByDockerCompose(self::KEYCLOAK_PATH);
$this->writer->endBlock();

return Command::SUCCESS;
}
}
Loading

0 comments on commit 67486a8

Please sign in to comment.