diff --git a/nixos/modules/virtualisation/openstack/common.nix b/nixos/modules/virtualisation/openstack/common.nix new file mode 100644 index 000000000000..3fce54a2fa59 --- /dev/null +++ b/nixos/modules/virtualisation/openstack/common.nix @@ -0,0 +1,54 @@ +{ lib }: + +with lib; + +rec { + # A shell script string helper to get the value of a secret at + # runtime. + getSecret = secretOption: + if secretOption.storage == "fromFile" + then ''$(cat ${secretOption.value})'' + else ''${secretOption.value}''; + + + # A shell script string help to replace at runtime in a file the + # pattern of a secret by its value. + replaceSecret = secretOption: filename: '' + sed -i "s/${secretOption.pattern}/${getSecret secretOption}/g" ${filename} + ''; + + # This generates an option that can be used to declare secrets which + # can be stored in the nix store, or not. A pattern is written in + # the nix store to represent the secret. The pattern can + # then be overwritten with the value of the secret at runtime. + mkSecretOption = {name, description ? ""}: + mkOption { + description = description; + type = types.submodule ({ + options = { + pattern = mkOption { + type = types.str; + default = "##${name}##"; + description = "The pattern that represent the secret."; + }; + storage = mkOption { + type = types.enum [ "fromNixStore" "fromFile" ]; + description = '' + Choose the way the password is provisionned. If + fromNixStore is used, the value is the password and it is + written in the nix store. If fromFile is used, the value + is a path from where the password will be read at + runtime. This is generally used with + deployment keys of Nixops. + '';}; + value = mkOption { + type = types.str; + description = '' + If the storage is fromNixStore, the value is the password itself, + otherwise it is a path to the file that contains the password. + ''; + }; + };}); + }; +} diff --git a/nixos/modules/virtualisation/openstack/keystone.nix b/nixos/modules/virtualisation/openstack/keystone.nix index 30bdb8690462..e32c5a4cae1b 100644 --- a/nixos/modules/virtualisation/openstack/keystone.nix +++ b/nixos/modules/virtualisation/openstack/keystone.nix @@ -1,22 +1,25 @@ { config, lib, pkgs, ... }: -with lib; +with lib; with import ./common.nix {inherit lib;}; let cfg = config.virtualisation.openstack.keystone; - keystoneConf = pkgs.writeText "keystone.conf" '' + keystoneConfTpl = pkgs.writeText "keystone.conf" '' [DEFAULT] - admin_token = ${cfg.adminToken} + admin_token = ${cfg.adminToken.pattern} policy_file=${cfg.package}/etc/policy.json [database] - connection = ${cfg.databaseConnection} + + 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 { @@ -44,9 +47,8 @@ in { ''; }; - adminToken = mkOption { - type = types.str; - default = "mySuperToken"; + adminToken = mkSecretOption { + name = "adminToken"; description = '' This is the admin token used to boostrap keystone, ie. to provision first resources. @@ -87,9 +89,8 @@ in { ''; }; - adminPassword = mkOption { - type = types.str; - default = "admin"; + adminPassword = mkSecretOption { + name = "keystoneAdminPassword"; description = '' The keystone admin user's password. ''; @@ -104,13 +105,34 @@ in { }; }; - databaseConnection = mkOption { + database = { + host = mkOption { type = types.str; - default = mysql://keystone:keystone@localhost/keystone; + default = "localhost"; description = '' - The SQLAlchemy connection string to use to connect to the - Keystone database. + 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";}; }; }; @@ -132,12 +154,19 @@ in { 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 + + 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 @@ -162,7 +191,7 @@ in { # 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} + export OS_SERVICE_TOKEN=${getSecret cfg.adminToken} # If the tenant service doesn't exist, we consider # keystone is not initialized @@ -170,7 +199,7 @@ in { 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 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 diff --git a/nixos/tests/keystone.nix b/nixos/tests/keystone.nix index 15e86db381fb..872d6c0784bf 100644 --- a/nixos/tests/keystone.nix +++ b/nixos/tests/keystone.nix @@ -4,13 +4,17 @@ with import ../lib/testing.nix { inherit system; }; with pkgs.lib; let + keystoneMysqlPassword = "keystoneMysqlPassword"; + keystoneMysqlPasswordFile = "/var/run/keystoneMysqlPassword"; + keystoneAdminPassword = "keystoneAdminPassword"; + 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'; + GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY '${keystoneMysqlPassword}'; + GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY '${keystoneMysqlPassword}'; ''; # 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"; + adminOpenstackCmd = "OS_TENANT_NAME=admin OS_USERNAME=admin OS_PASSWORD=${keystoneAdminPassword} 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"; @@ -18,12 +22,34 @@ in makeTest { machine = { config, pkgs, ... }: { + # This is to simulate nixops deployment process. + # https://nixos.org/nixops/manual/#opt-deployment.keys + boot.postBootCommands = "echo ${keystoneMysqlPassword} > ${keystoneMysqlPasswordFile}"; + services.mysql.enable = true; services.mysql.initialScript = createKeystoneDb; virtualisation = { - openstack.keystone.enable = true; - openstack.keystone.bootstrap.enable = true; + + openstack.keystone = { + enable = true; + # Check if we can get the secret from a file + database.password = { + value = keystoneMysqlPasswordFile; + storage = "fromFile"; + }; + adminToken = { + value = "adminToken"; + storage = "fromNixStore"; + }; + + bootstrap.enable = true; + # Check if we can get the secret from the store + bootstrap.adminPassword = { + value = keystoneAdminPassword; + storage = "fromNixStore"; + }; + }; memorySize = 2096; diskSize = 4 * 1024;