Browse Source

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.
pjones/monitoring
Peter J. Jones 7 months ago
parent
commit
193b82189e
Signed by: Peter Jones <pjones@devalot.com> GPG Key ID: 9DAFAA8D01941E49

+ 1
- 0
.gitignore View File

@@ -0,0 +1 @@
/result

+ 19
- 5
default.nix 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
- 0
lib/keys.nix 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

+ 18
- 0
modules/default.nix 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;
};
}

+ 5
- 12
modules/services/databases/postgresql/default.nix 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.
'';
};


+ 21
- 4
modules/services/web/rails/default.nix 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}";
};
};
};

+ 17
- 12
modules/services/web/rails/options.nix 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 = {

Loading…
Cancel
Save