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.
This commit is contained in:
Antoine Eiche 2016-12-10 23:14:50 +01:00 committed by Jörg Thalheim
parent 415c9ff90b
commit a932f68d9c
3 changed files with 131 additions and 22 deletions

View File

@ -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.
'';
};
};});
};
}

View File

@ -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,14 +105,35 @@ 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";};
};
};
config = mkIf cfg.enable {
@ -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

View File

@ -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;