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"); + ''; +}