diff --git a/nixos/modules/misc/ids.nix b/nixos/modules/misc/ids.nix
index 2a9a18236e5..70705771183 100644
--- a/nixos/modules/misc/ids.nix
+++ b/nixos/modules/misc/ids.nix
@@ -280,6 +280,7 @@
stanchion = 262;
riak-cs = 263;
infinoted = 264;
+ keystone = 265;
# When adding a uid, make sure it doesn't match an existing gid. And don't use uids above 399!
@@ -530,6 +531,7 @@
stanchion = 262;
riak-cs = 263;
infinoted = 264;
+ keystone = 265;
# When adding a gid, make sure it doesn't match an existing
# uid. Users and groups with the same name should have equal
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index 4032df9fb91..cd12fe4f9b3 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -631,4 +631,5 @@
./virtualisation/vmware-guest.nix
./virtualisation/xen-dom0.nix
./virtualisation/xe-guest-utilities.nix
+ ./virtualisation/openstack/keystone.nix
]
diff --git a/nixos/modules/virtualisation/openstack/keystone.nix b/nixos/modules/virtualisation/openstack/keystone.nix
new file mode 100644
index 00000000000..30bdb869046
--- /dev/null
+++ b/nixos/modules/virtualisation/openstack/keystone.nix
@@ -0,0 +1,191 @@
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.virtualisation.openstack.keystone;
+ keystoneConf = pkgs.writeText "keystone.conf" ''
+ [DEFAULT]
+ admin_token = ${cfg.adminToken}
+ policy_file=${cfg.package}/etc/policy.json
+
+ [database]
+ connection = ${cfg.databaseConnection}
+
+ [paste_deploy]
+ config_file = ${cfg.package}/etc/keystone-paste.ini
+
+ ${cfg.extraConfig}
+ '';
+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 = mkOption {
+ type = types.str;
+ default = "mySuperToken";
+ 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 = mkOption {
+ type = types.str;
+ default = "admin";
+ description = ''
+ The keystone admin user's password.
+ '';
+ };
+
+ adminTenant = mkOption {
+ type = types.str;
+ default = "admin";
+ description = ''
+ A keystone admin tenant name.
+ '';
+ };
+ };
+
+ databaseConnection = mkOption {
+ type = types.str;
+ default = mysql://keystone:keystone@localhost/keystone;
+ description = ''
+ The SQLAlchemy connection string to use to connect to the
+ Keystone database.
+ '';
+ };
+ };
+
+ 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";
+ packages = [ mysql ];
+ 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
+ # 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=${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 ${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}";
+ };
+ };
+ };
+}
diff --git a/nixos/tests/keystone.nix b/nixos/tests/keystone.nix
new file mode 100644
index 00000000000..15e86db381f
--- /dev/null
+++ b/nixos/tests/keystone.nix
@@ -0,0 +1,53 @@
+{ system ? builtins.currentSystem }:
+
+with import ../lib/testing.nix { inherit system; };
+with pkgs.lib;
+
+let
+ createKeystoneDb = pkgs.writeText "create-keystone-db.sql" ''
+ create database keystone;
+ GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone';
+ GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone';
+ '';
+ # The admin keystone account
+ adminOpenstackCmd = "OS_TENANT_NAME=admin OS_USERNAME=admin OS_PASSWORD=admin OS_AUTH_URL=http://localhost:5000/v3 OS_IDENTITY_API_VERSION=3 openstack";
+ # The created demo keystone account
+ demoOpenstackCmd = "OS_TENANT_NAME=demo OS_USERNAME=demo OS_PASSWORD=demo OS_AUTH_URL=http://localhost:5000/v3 OS_IDENTITY_API_VERSION=3 openstack";
+
+in makeTest {
+ machine =
+ { config, pkgs, ... }:
+ {
+ services.mysql.enable = true;
+ services.mysql.initialScript = createKeystoneDb;
+
+ virtualisation = {
+ openstack.keystone.enable = true;
+ openstack.keystone.bootstrap.enable = true;
+
+ memorySize = 2096;
+ diskSize = 4 * 1024;
+ };
+
+ environment.systemPackages = with pkgs.pythonPackages; with pkgs; [
+ openstackclient
+ ];
+ };
+
+ testScript =
+ ''
+ $machine->waitForUnit("keystone-all.service");
+
+ # Verify that admin ccount is working
+ $machine->succeed("${adminOpenstackCmd} token issue");
+
+ # Try to create a new user
+ $machine->succeed("${adminOpenstackCmd} project create --domain default --description 'Demo Project' demo");
+ $machine->succeed("${adminOpenstackCmd} user create --domain default --password demo demo");
+ $machine->succeed("${adminOpenstackCmd} role create user");
+ $machine->succeed("${adminOpenstackCmd} role add --project demo --user demo user");
+
+ # Verify this new account is working
+ $machine->succeed("${demoOpenstackCmd} token issue");
+ '';
+}