summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--nixos/modules/virtualisation/openstack/common.nix54
-rw-r--r--nixos/modules/virtualisation/openstack/keystone.nix63
-rw-r--r--nixos/tests/keystone.nix36
3 files changed, 131 insertions, 22 deletions
diff --git a/nixos/modules/virtualisation/openstack/common.nix b/nixos/modules/virtualisation/openstack/common.nix
new file mode 100644
index 00000000000..3fce54a2fa5
--- /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 <link
+            xlink:href="https://nixos.org/nixops/manual/#opt-deployment.keys">
+            deployment keys</link> 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 30bdb869046..e32c5a4cae1 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 = "localhost";
+        description = ''
+          Host of the database.
+        '';
+      };
+
+      name = mkOption {
         type = types.str;
-        default = mysql://keystone:keystone@localhost/keystone;
+        default = "keystone";
         description = ''
-          The SQLAlchemy connection string to use to connect to the
-          Keystone database.
+          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 15e86db381f..872d6c0784b 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;