lol

nixos/keystone: secrets can be read from files

A secret can be stored in a file. It is written at runtime in the
configuration file.
Note it is also possible to write them in the nix store for dev
purposes.

authored by

Antoine Eiche and committed by
Jörg Thalheim
a932f68d 415c9ff9

+131 -22
+54
nixos/modules/virtualisation/openstack/common.nix
···
··· 1 + { lib }: 2 + 3 + with lib; 4 + 5 + rec { 6 + # A shell script string helper to get the value of a secret at 7 + # runtime. 8 + getSecret = secretOption: 9 + if secretOption.storage == "fromFile" 10 + then ''$(cat ${secretOption.value})'' 11 + else ''${secretOption.value}''; 12 + 13 + 14 + # A shell script string help to replace at runtime in a file the 15 + # pattern of a secret by its value. 16 + replaceSecret = secretOption: filename: '' 17 + sed -i "s/${secretOption.pattern}/${getSecret secretOption}/g" ${filename} 18 + ''; 19 + 20 + # This generates an option that can be used to declare secrets which 21 + # can be stored in the nix store, or not. A pattern is written in 22 + # the nix store to represent the secret. The pattern can 23 + # then be overwritten with the value of the secret at runtime. 24 + mkSecretOption = {name, description ? ""}: 25 + mkOption { 26 + description = description; 27 + type = types.submodule ({ 28 + options = { 29 + pattern = mkOption { 30 + type = types.str; 31 + default = "##${name}##"; 32 + description = "The pattern that represent the secret."; 33 + }; 34 + storage = mkOption { 35 + type = types.enum [ "fromNixStore" "fromFile" ]; 36 + description = '' 37 + Choose the way the password is provisionned. If 38 + fromNixStore is used, the value is the password and it is 39 + written in the nix store. If fromFile is used, the value 40 + is a path from where the password will be read at 41 + runtime. This is generally used with <link 42 + xlink:href="https://nixos.org/nixops/manual/#opt-deployment.keys"> 43 + deployment keys</link> of Nixops. 44 + '';}; 45 + value = mkOption { 46 + type = types.str; 47 + description = '' 48 + If the storage is fromNixStore, the value is the password itself, 49 + otherwise it is a path to the file that contains the password. 50 + ''; 51 + }; 52 + };}); 53 + }; 54 + }
+46 -17
nixos/modules/virtualisation/openstack/keystone.nix
··· 1 { config, lib, pkgs, ... }: 2 3 - with lib; 4 5 let 6 cfg = config.virtualisation.openstack.keystone; 7 - keystoneConf = pkgs.writeText "keystone.conf" '' 8 [DEFAULT] 9 - admin_token = ${cfg.adminToken} 10 policy_file=${cfg.package}/etc/policy.json 11 12 [database] 13 - connection = ${cfg.databaseConnection} 14 15 [paste_deploy] 16 config_file = ${cfg.package}/etc/keystone-paste.ini 17 18 ${cfg.extraConfig} 19 ''; 20 in { 21 options.virtualisation.openstack.keystone = { 22 package = mkOption { ··· 44 ''; 45 }; 46 47 - adminToken = mkOption { 48 - type = types.str; 49 - default = "mySuperToken"; 50 description = '' 51 This is the admin token used to boostrap keystone, 52 ie. to provision first resources. ··· 87 ''; 88 }; 89 90 - adminPassword = mkOption { 91 - type = types.str; 92 - default = "admin"; 93 description = '' 94 The keystone admin user's password. 95 ''; ··· 104 }; 105 }; 106 107 - databaseConnection = mkOption { 108 type = types.str; 109 - default = mysql://keystone:keystone@localhost/keystone; 110 description = '' 111 - The SQLAlchemy connection string to use to connect to the 112 - Keystone database. 113 ''; 114 }; 115 }; 116 ··· 132 133 systemd.services.keystone-all = { 134 description = "OpenStack Keystone Daemon"; 135 - packages = [ mysql ]; 136 after = [ "network.target"]; 137 path = [ cfg.package pkgs.mysql pkgs.curl pkgs.pythonPackages.keystoneclient pkgs.gawk ]; 138 wantedBy = [ "multi-user.target" ]; 139 preStart = '' 140 mkdir -m 755 -p /var/lib/keystone 141 # Initialise the database 142 ${cfg.package}/bin/keystone-manage --config-file=${keystoneConf} db_sync 143 # Set up the keystone's PKI infrastructure ··· 162 163 # We use the service token to create a first admin user 164 export OS_SERVICE_ENDPOINT=http://localhost:35357/v2.0 165 - export OS_SERVICE_TOKEN=${cfg.adminToken} 166 167 # If the tenant service doesn't exist, we consider 168 # keystone is not initialized ··· 170 then 171 keystone tenant-create --name service 172 keystone tenant-create --name ${cfg.bootstrap.adminTenant} 173 - keystone user-create --name ${cfg.bootstrap.adminUsername} --tenant ${cfg.bootstrap.adminTenant} --pass ${cfg.bootstrap.adminPassword} 174 keystone role-create --name admin 175 keystone role-create --name Member 176 keystone user-role-add --tenant ${cfg.bootstrap.adminTenant} --user ${cfg.bootstrap.adminUsername} --role admin
··· 1 { config, lib, pkgs, ... }: 2 3 + with lib; with import ./common.nix {inherit lib;}; 4 5 let 6 cfg = config.virtualisation.openstack.keystone; 7 + keystoneConfTpl = pkgs.writeText "keystone.conf" '' 8 [DEFAULT] 9 + admin_token = ${cfg.adminToken.pattern} 10 policy_file=${cfg.package}/etc/policy.json 11 12 [database] 13 + 14 + connection = "mysql://${cfg.database.user}:${cfg.database.password.pattern}@${cfg.database.host}/${cfg.database.name}" 15 16 [paste_deploy] 17 config_file = ${cfg.package}/etc/keystone-paste.ini 18 19 ${cfg.extraConfig} 20 ''; 21 + keystoneConf = "/var/lib/keystone/keystone.conf"; 22 + 23 in { 24 options.virtualisation.openstack.keystone = { 25 package = mkOption { ··· 47 ''; 48 }; 49 50 + adminToken = mkSecretOption { 51 + name = "adminToken"; 52 description = '' 53 This is the admin token used to boostrap keystone, 54 ie. to provision first resources. ··· 89 ''; 90 }; 91 92 + adminPassword = mkSecretOption { 93 + name = "keystoneAdminPassword"; 94 description = '' 95 The keystone admin user's password. 96 ''; ··· 105 }; 106 }; 107 108 + database = { 109 + host = mkOption { 110 + type = types.str; 111 + default = "localhost"; 112 + description = '' 113 + Host of the database. 114 + ''; 115 + }; 116 + 117 + name = mkOption { 118 + type = types.str; 119 + default = "keystone"; 120 + description = '' 121 + Name of the existing database. 122 + ''; 123 + }; 124 + 125 + user = mkOption { 126 type = types.str; 127 + default = "keystone"; 128 description = '' 129 + The database user. The user must exist and has access to 130 + the specified database. 131 ''; 132 + }; 133 + password = mkSecretOption { 134 + name = "mysqlPassword"; 135 + description = "The database user's password";}; 136 }; 137 }; 138 ··· 154 155 systemd.services.keystone-all = { 156 description = "OpenStack Keystone Daemon"; 157 after = [ "network.target"]; 158 path = [ cfg.package pkgs.mysql pkgs.curl pkgs.pythonPackages.keystoneclient pkgs.gawk ]; 159 wantedBy = [ "multi-user.target" ]; 160 preStart = '' 161 mkdir -m 755 -p /var/lib/keystone 162 + 163 + cp ${keystoneConfTpl} ${keystoneConf}; 164 + chown keystone:keystone ${keystoneConf}; 165 + chmod 640 ${keystoneConf} 166 + 167 + ${replaceSecret cfg.database.password keystoneConf} 168 + ${replaceSecret cfg.adminToken keystoneConf} 169 + 170 # Initialise the database 171 ${cfg.package}/bin/keystone-manage --config-file=${keystoneConf} db_sync 172 # Set up the keystone's PKI infrastructure ··· 191 192 # We use the service token to create a first admin user 193 export OS_SERVICE_ENDPOINT=http://localhost:35357/v2.0 194 + export OS_SERVICE_TOKEN=${getSecret cfg.adminToken} 195 196 # If the tenant service doesn't exist, we consider 197 # keystone is not initialized ··· 199 then 200 keystone tenant-create --name service 201 keystone tenant-create --name ${cfg.bootstrap.adminTenant} 202 + keystone user-create --name ${cfg.bootstrap.adminUsername} --tenant ${cfg.bootstrap.adminTenant} --pass ${getSecret cfg.bootstrap.adminPassword} 203 keystone role-create --name admin 204 keystone role-create --name Member 205 keystone user-role-add --tenant ${cfg.bootstrap.adminTenant} --user ${cfg.bootstrap.adminUsername} --role admin
+31 -5
nixos/tests/keystone.nix
··· 4 with pkgs.lib; 5 6 let 7 createKeystoneDb = pkgs.writeText "create-keystone-db.sql" '' 8 create database keystone; 9 - GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY 'keystone'; 10 - GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY 'keystone'; 11 ''; 12 # The admin keystone account 13 - adminOpenstackCmd = "OS_TENANT_NAME=admin OS_USERNAME=admin OS_PASSWORD=admin OS_AUTH_URL=http://localhost:5000/v3 OS_IDENTITY_API_VERSION=3 openstack"; 14 # The created demo keystone account 15 demoOpenstackCmd = "OS_TENANT_NAME=demo OS_USERNAME=demo OS_PASSWORD=demo OS_AUTH_URL=http://localhost:5000/v3 OS_IDENTITY_API_VERSION=3 openstack"; 16 ··· 18 machine = 19 { config, pkgs, ... }: 20 { 21 services.mysql.enable = true; 22 services.mysql.initialScript = createKeystoneDb; 23 24 virtualisation = { 25 - openstack.keystone.enable = true; 26 - openstack.keystone.bootstrap.enable = true; 27 28 memorySize = 2096; 29 diskSize = 4 * 1024;
··· 4 with pkgs.lib; 5 6 let 7 + keystoneMysqlPassword = "keystoneMysqlPassword"; 8 + keystoneMysqlPasswordFile = "/var/run/keystoneMysqlPassword"; 9 + keystoneAdminPassword = "keystoneAdminPassword"; 10 + 11 createKeystoneDb = pkgs.writeText "create-keystone-db.sql" '' 12 create database keystone; 13 + GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY '${keystoneMysqlPassword}'; 14 + GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY '${keystoneMysqlPassword}'; 15 ''; 16 # The admin keystone account 17 + adminOpenstackCmd = "OS_TENANT_NAME=admin OS_USERNAME=admin OS_PASSWORD=${keystoneAdminPassword} OS_AUTH_URL=http://localhost:5000/v3 OS_IDENTITY_API_VERSION=3 openstack"; 18 # The created demo keystone account 19 demoOpenstackCmd = "OS_TENANT_NAME=demo OS_USERNAME=demo OS_PASSWORD=demo OS_AUTH_URL=http://localhost:5000/v3 OS_IDENTITY_API_VERSION=3 openstack"; 20 ··· 22 machine = 23 { config, pkgs, ... }: 24 { 25 + # This is to simulate nixops deployment process. 26 + # https://nixos.org/nixops/manual/#opt-deployment.keys 27 + boot.postBootCommands = "echo ${keystoneMysqlPassword} > ${keystoneMysqlPasswordFile}"; 28 + 29 services.mysql.enable = true; 30 services.mysql.initialScript = createKeystoneDb; 31 32 virtualisation = { 33 + 34 + openstack.keystone = { 35 + enable = true; 36 + # Check if we can get the secret from a file 37 + database.password = { 38 + value = keystoneMysqlPasswordFile; 39 + storage = "fromFile"; 40 + }; 41 + adminToken = { 42 + value = "adminToken"; 43 + storage = "fromNixStore"; 44 + }; 45 + 46 + bootstrap.enable = true; 47 + # Check if we can get the secret from the store 48 + bootstrap.adminPassword = { 49 + value = keystoneAdminPassword; 50 + storage = "fromNixStore"; 51 + }; 52 + }; 53 54 memorySize = 2096; 55 diskSize = 4 * 1024;