Automatically depending on NixOps key services, new Rails sourcedFile option

* Services that need password files will automatically depend on the
    appropriate NixOps key service as necessary.

  * New `sourcedFile` option for Rails applications to load a Bash
    script just before starting the Rails service.  Useful for setting
    secret environment variables.
This commit is contained in:
Peter Jones 2019-01-03 14:33:38 -07:00
parent accdc1bf54
commit 193b82189e
No known key found for this signature in database
GPG key ID: 9DAFAA8D01941E49
7 changed files with 124 additions and 33 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/result

View file

@ -1,7 +1,21 @@
{ config, lib, pkgs, ...}:
{ pkgs ? import <nixpkgs> { }
, ...
}:
{
imports = [
./modules
];
pkgs.stdenvNoCC.mkDerivation rec {
name = "phoebe-${version}";
version = "0.1";
src = ./.;
phases =
[ "unpackPhase"
"installPhase"
"fixupPhase"
];
installPhase = ''
mkdir -p $out
cp -rp bin modules lib $out/
chmod 0555 $out/bin/*
'';
}

43
lib/keys.nix Normal file
View file

@ -0,0 +1,43 @@
# Functions for working with NixOps keys.
{ lib }:
with lib;
let
# Where NixOps stores keys:
keyDirectory = "/run/keys/";
# Generate a service name:
mkServiceName = path:
replaceStrings ["/"] ["-"]
(removePrefix keyDirectory path + "-key.service");
funcs = rec {
/* Test to see if a file path is a NixOps managed key.
Example:
isKeyFile "/run/keys/foo"
=> true
isKeyFile "/etc/passwd"
=> false
*/
isKeyFile = path:
if path == null
then false
else hasPrefix keyDirectory path;
/* Returns an array containing a systemd service name that can be
used to add a 'wants' or 'after' entry for a NixOps key.
Example:
keyService "/run/keys/foo"
=> ["foo.service"]
keyService "/etc/passwd"
=> []
*/
keyService = path: optional (isKeyFile path) (mkServiceName path);
};
in funcs

View file

@ -1,8 +1,26 @@
{ config, lib, pkgs, ...}:
with lib;
let
libFiles = [
../lib/keys.nix
];
loadLib = path: import path { inherit lib; };
libs = foldr (a: b: recursiveUpdate (loadLib a) b) {} libFiles;
in
{
imports = [
./security
./services
];
options.phoebe.lib = mkOption {
type = types.attrs;
default = libs;
internal = true;
readOnly = true;
};
}

View file

@ -6,9 +6,10 @@ with lib;
let
cfg = config.phoebe.services.postgresql;
plib = config.phoebe.lib;
superuser = config.services.postgresql.superUser;
create-user = import ./create-user.nix { inherit config lib pkgs; };
afterservices = concatMap (a: a.afterServices) (attrValues cfg.accounts);
afterservices = concatMap (a: plib.keyService a.passwordFile) (attrValues cfg.accounts);
# Per-account options:
account = { name, ... }: {
@ -30,18 +31,10 @@ let
A file containing the password of this database user.
You'll want to use something like NixOps to get the password
file onto the target machine.
'';
};
afterServices = mkOption {
type = types.listOf types.str;
default = [ ];
example = [ "dbpassword.service" ];
description = ''
A list of services that need to run before this user account
can be created. This is really useful if you are using
NixOps to deploy the password file and want to wait for the
key to appear in /run/keys.
If the file looks like it's a NixOps key then the account
creation script will automatically wait for the appropriate
key service to start.
'';
};

View file

@ -8,6 +8,7 @@ let
##############################################################################
# Save some typing.
cfg = config.phoebe.services.rails;
plib = config.phoebe.lib;
scripts = import ./scripts.nix { inherit lib pkgs; };
options = import ./options.nix { inherit config lib pkgs; };
@ -70,10 +71,17 @@ let
} // app.environment;
wantedBy = [ "multi-user.target" ];
wants = optional (app.database.passwordService != null) app.database.passwordService;
after = [ "network.target" ] ++
wants =
plib.keyService app.database.passwordFile ++
plib.keyService app.sourcedFile;
after =
[ "network.target" ] ++
optional localpg "postgresql.service" ++
optional (app.database.passwordService != null) app.database.passwordService;
optional localpg "pg-accounts.service" ++
plib.keyService app.database.passwordFile ++
plib.keyService app.sourcedFile;
preStart = ''
# Prepare the config directory:
@ -88,6 +96,11 @@ let
mkdir -p ${app.home}/home
ln -nfs ${app.package}/share/${app.name} ${app.home}/home/${app.name}
# Copy the sourcedFile if necessary:
${optionalString (app.sourcedFile != null) ''
cp ${app.sourcedFile} ${app.home}/state/sourcedFile.sh
''}
# Fix permissions:
chown -R rails-${app.name}:rails-${app.name} ${app.home}
chmod go+rx $(dirname "${app.home}")
@ -101,6 +114,11 @@ let
-s ${app.home}/state
'';
script = ''
${optionalString (app.sourcedFile != null) ". ${app.home}/state/sourcedFile.sh"}
${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}
'';
serviceConfig = {
WorkingDirectory = "${app.package}/share/${app.name}";
Restart = "on-failure";
@ -110,7 +128,6 @@ let
User = "rails-${app.name}";
Group = "rails-${app.name}";
UMask = "0077";
ExecStart = "${app.package.rubyEnv}/bin/puma -e ${app.railsEnv} -p ${toString app.port}";
};
};
};

View file

@ -31,18 +31,6 @@ let
'';
};
passwordService = mkOption {
type = types.nullOr types.str;
default = null;
example = "db-password.service";
description = ''
A service to wait on before starting the Rails application.
This service should provide the password file for the
passwordFile option. Useful when deploying passwords with
NixOps.
'';
};
migrate = mkOption {
type = types.bool;
default = true;
@ -112,6 +100,23 @@ let
default = { };
description = "Environment variables.";
};
sourcedFile = mkOption {
type = types.nullOr types.path;
default = null;
example = "/run/keys/env.sh";
description = ''
Bash file to source immediately before running any service
command.
If the file is store under /run/keys the service will wait
for the file to become available.
This option can be used to set environment variables more
securely than using the environment option. However, you
should really use the Rails secrets system.
'';
};
};
config = {