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 1 { config, lib, pkgs, ... }: 2 2 3 - with lib; 3 + with lib; with import ./common.nix {inherit lib;}; 4 4 5 5 let 6 6 cfg = config.virtualisation.openstack.keystone; 7 - keystoneConf = pkgs.writeText "keystone.conf" '' 7 + keystoneConfTpl = pkgs.writeText "keystone.conf" '' 8 8 [DEFAULT] 9 - admin_token = ${cfg.adminToken} 9 + admin_token = ${cfg.adminToken.pattern} 10 10 policy_file=${cfg.package}/etc/policy.json 11 11 12 12 [database] 13 - connection = ${cfg.databaseConnection} 13 + 14 + connection = "mysql://${cfg.database.user}:${cfg.database.password.pattern}@${cfg.database.host}/${cfg.database.name}" 14 15 15 16 [paste_deploy] 16 17 config_file = ${cfg.package}/etc/keystone-paste.ini 17 18 18 19 ${cfg.extraConfig} 19 20 ''; 21 + keystoneConf = "/var/lib/keystone/keystone.conf"; 22 + 20 23 in { 21 24 options.virtualisation.openstack.keystone = { 22 25 package = mkOption { ··· 44 47 ''; 45 48 }; 46 49 47 - adminToken = mkOption { 48 - type = types.str; 49 - default = "mySuperToken"; 50 + adminToken = mkSecretOption { 51 + name = "adminToken"; 50 52 description = '' 51 53 This is the admin token used to boostrap keystone, 52 54 ie. to provision first resources. ··· 87 89 ''; 88 90 }; 89 91 90 - adminPassword = mkOption { 91 - type = types.str; 92 - default = "admin"; 92 + adminPassword = mkSecretOption { 93 + name = "keystoneAdminPassword"; 93 94 description = '' 94 95 The keystone admin user's password. 95 96 ''; ··· 104 105 }; 105 106 }; 106 107 107 - databaseConnection = mkOption { 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 { 108 126 type = types.str; 109 - default = mysql://keystone:keystone@localhost/keystone; 127 + default = "keystone"; 110 128 description = '' 111 - The SQLAlchemy connection string to use to connect to the 112 - Keystone database. 129 + The database user. The user must exist and has access to 130 + the specified database. 113 131 ''; 132 + }; 133 + password = mkSecretOption { 134 + name = "mysqlPassword"; 135 + description = "The database user's password";}; 114 136 }; 115 137 }; 116 138 ··· 132 154 133 155 systemd.services.keystone-all = { 134 156 description = "OpenStack Keystone Daemon"; 135 - packages = [ mysql ]; 136 157 after = [ "network.target"]; 137 158 path = [ cfg.package pkgs.mysql pkgs.curl pkgs.pythonPackages.keystoneclient pkgs.gawk ]; 138 159 wantedBy = [ "multi-user.target" ]; 139 160 preStart = '' 140 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 + 141 170 # Initialise the database 142 171 ${cfg.package}/bin/keystone-manage --config-file=${keystoneConf} db_sync 143 172 # Set up the keystone's PKI infrastructure ··· 162 191 163 192 # We use the service token to create a first admin user 164 193 export OS_SERVICE_ENDPOINT=http://localhost:35357/v2.0 165 - export OS_SERVICE_TOKEN=${cfg.adminToken} 194 + export OS_SERVICE_TOKEN=${getSecret cfg.adminToken} 166 195 167 196 # If the tenant service doesn't exist, we consider 168 197 # keystone is not initialized ··· 170 199 then 171 200 keystone tenant-create --name service 172 201 keystone tenant-create --name ${cfg.bootstrap.adminTenant} 173 - keystone user-create --name ${cfg.bootstrap.adminUsername} --tenant ${cfg.bootstrap.adminTenant} --pass ${cfg.bootstrap.adminPassword} 202 + keystone user-create --name ${cfg.bootstrap.adminUsername} --tenant ${cfg.bootstrap.adminTenant} --pass ${getSecret cfg.bootstrap.adminPassword} 174 203 keystone role-create --name admin 175 204 keystone role-create --name Member 176 205 keystone user-role-add --tenant ${cfg.bootstrap.adminTenant} --user ${cfg.bootstrap.adminUsername} --role admin
+31 -5
nixos/tests/keystone.nix
··· 4 4 with pkgs.lib; 5 5 6 6 let 7 + keystoneMysqlPassword = "keystoneMysqlPassword"; 8 + keystoneMysqlPasswordFile = "/var/run/keystoneMysqlPassword"; 9 + keystoneAdminPassword = "keystoneAdminPassword"; 10 + 7 11 createKeystoneDb = pkgs.writeText "create-keystone-db.sql" '' 8 12 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'; 13 + GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'localhost' IDENTIFIED BY '${keystoneMysqlPassword}'; 14 + GRANT ALL PRIVILEGES ON keystone.* TO 'keystone'@'%' IDENTIFIED BY '${keystoneMysqlPassword}'; 11 15 ''; 12 16 # 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"; 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"; 14 18 # The created demo keystone account 15 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"; 16 20 ··· 18 22 machine = 19 23 { config, pkgs, ... }: 20 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 + 21 29 services.mysql.enable = true; 22 30 services.mysql.initialScript = createKeystoneDb; 23 31 24 32 virtualisation = { 25 - openstack.keystone.enable = true; 26 - openstack.keystone.bootstrap.enable = true; 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 + }; 27 53 28 54 memorySize = 2096; 29 55 diskSize = 4 * 1024;