{ config, lib, pkgs, ... }:
with lib; with import ./common.nix {inherit lib;};
let
cfg = config.virtualisation.openstack.keystone;
keystoneConfTpl = pkgs.writeText "keystone.conf" ''
[DEFAULT]
admin_token = ${cfg.adminToken.pattern}
policy_file=${cfg.package}/etc/policy.json
[database]
connection = "mysql://${cfg.database.user}:${cfg.database.password.pattern}@${cfg.database.host}/${cfg.database.name}"
[paste_deploy]
config_file = ${cfg.package}/etc/keystone-paste.ini
${cfg.extraConfig}
'';
keystoneConf = "/var/lib/keystone/keystone.conf";
in {
options.virtualisation.openstack.keystone = {
package = mkOption {
type = types.package;
example = literalExample "pkgs.keystone";
description = ''
Keystone package to use.
'';
};
enable = mkOption {
default = false;
type = types.bool;
description = ''
Enable Keystone, the OpenStack Identity Service
'';
};
extraConfig = mkOption {
default = "";
type = types.lines;
description = ''
Additional text appended to keystone.conf,
the main Keystone configuration file.
'';
};
adminToken = mkSecretOption {
name = "adminToken";
description = ''
This is the admin token used to boostrap keystone,
ie. to provision first resources.
'';
};
bootstrap = {
enable = mkOption {
default = false;
type = types.bool;
description = ''
Bootstrap the Keystone service by creating the service
tenant, an admin account and a public endpoint. This options
provides a ready-to-use admin account. This is only done at
the first Keystone execution by the systemd post start.
Note this option is a helper for setting up development or
testing environments.
'';
};
endpointPublic = mkOption {
type = types.str;
default = "http://localhost:5000/v2.0";
description = ''
The public identity endpoint. The link
create keystone endpoint provides more informations
about that.
'';
};
adminUsername = mkOption {
type = types.str;
default = "admin";
description = ''
A keystone admin username.
'';
};
adminPassword = mkSecretOption {
name = "keystoneAdminPassword";
description = ''
The keystone admin user's password.
'';
};
adminTenant = mkOption {
type = types.str;
default = "admin";
description = ''
A keystone admin tenant name.
'';
};
};
database = {
host = mkOption {
type = types.str;
default = "localhost";
description = ''
Host of the database.
'';
};
name = mkOption {
type = types.str;
default = "keystone";
description = ''
Name of the existing database.
'';
};
user = mkOption {
type = types.str;
default = "keystone";
description = ''
The database user. The user must exist and has access to
the specified database.
'';
};
password = mkSecretOption {
name = "mysqlPassword";
description = "The database user's password";};
};
};
config = mkIf cfg.enable {
# Note: when changing the default, make it conditional on
# ‘system.stateVersion’ to maintain compatibility with existing
# systems!
virtualisation.openstack.keystone.package = mkDefault pkgs.keystone;
users.extraUsers = [{
name = "keystone";
group = "keystone";
uid = config.ids.uids.keystone;
}];
users.extraGroups = [{
name = "keystone";
gid = config.ids.gids.keystone;
}];
systemd.services.keystone-all = {
description = "OpenStack Keystone Daemon";
after = [ "network.target"];
path = [ cfg.package pkgs.mysql pkgs.curl pkgs.pythonPackages.keystoneclient pkgs.gawk ];
wantedBy = [ "multi-user.target" ];
preStart = ''
mkdir -m 755 -p /var/lib/keystone
cp ${keystoneConfTpl} ${keystoneConf};
chown keystone:keystone ${keystoneConf};
chmod 640 ${keystoneConf}
${replaceSecret cfg.database.password keystoneConf}
${replaceSecret cfg.adminToken keystoneConf}
# Initialise the database
${cfg.package}/bin/keystone-manage --config-file=${keystoneConf} db_sync
# Set up the keystone's PKI infrastructure
${cfg.package}/bin/keystone-manage --config-file=${keystoneConf} pki_setup --keystone-user keystone --keystone-group keystone
'';
postStart = optionalString cfg.bootstrap.enable ''
set -eu
# Wait until the keystone is available for use
count=0
while ! curl --fail -s http://localhost:35357/v2.0 > /dev/null
do
if [ $count -eq 30 ]
then
echo "Tried 30 times, giving up..."
exit 1
fi
echo "Keystone not yet started. Waiting for 1 second..."
count=$((count++))
sleep 1
done
# We use the service token to create a first admin user
export OS_SERVICE_ENDPOINT=http://localhost:35357/v2.0
export OS_SERVICE_TOKEN=${getSecret cfg.adminToken}
# If the tenant service doesn't exist, we consider
# keystone is not initialized
if ! keystone tenant-get service
then
keystone tenant-create --name service
keystone tenant-create --name ${cfg.bootstrap.adminTenant}
keystone user-create --name ${cfg.bootstrap.adminUsername} --tenant ${cfg.bootstrap.adminTenant} --pass ${getSecret cfg.bootstrap.adminPassword}
keystone role-create --name admin
keystone role-create --name Member
keystone user-role-add --tenant ${cfg.bootstrap.adminTenant} --user ${cfg.bootstrap.adminUsername} --role admin
keystone service-create --type identity --name keystone
ID=$(keystone service-get keystone | awk '/ id / { print $4 }')
keystone endpoint-create --region RegionOne --service $ID --publicurl ${cfg.bootstrap.endpointPublic} --adminurl http://localhost:35357/v2.0 --internalurl http://localhost:5000/v2.0
fi
'';
serviceConfig = {
PermissionsStartOnly = true; # preStart must be run as root
TimeoutStartSec = "600"; # 10min for initial db migrations
User = "keystone";
Group = "keystone";
ExecStart = "${cfg.package}/bin/keystone-all --config-file=${keystoneConf}";
};
};
};
}