Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

144 changes: 144 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,151 @@
systems = {
"x86_64-linux" = [ "linux" "x64" ];
};

in
{
nixosModules.default = { config, ... }: with nixpkgs; with lib;
{
options = {
services.polykey = {
enable = mkEnableOption "Enable the Polykey agent. Users with the `polykey` group or root permissions will be able to manage the agent.";

passwordFilePath = mkOption {
type = with types; uniq str;
description = ''
The path to the Polykey password file. This is required to be set for the module to work, otherwise this module will fail.
'';
};

recoveryCodeFilePath = mkOption {
type = with types; uniq str;
default = "";
description = ''
The path to the Polykey recovery code file. This is not required, but if set will read a recovery code from the provided path to bootstrap a new state with.
'';
};

recoveryCodeOutPath = mkOption {
type = with types; uniq str;
description = ''
The path to the Polykey recovery code file output location.
'';
};

statePath = mkOption {
type = with types; uniq str;
default = "/var/lib/polykey";
description = "The path to the Polykey node state directory. Will default to `/var/lib/polykey`, but can be overwritten to a custom path.";
};
};
programs.polykey = {
enable = mkEnableOption "Enable the per-user Polykey agent.";

passwordFilePath = mkOption {
type = with types; uniq str;
description = ''
The path to the Polykey password file. This is required to be set for the module to work, otherwise this module will fail.
'';
};

recoveryCodeFilePath = mkOption {
type = with types; uniq str;
default = "";
description = ''
The path to the Polykey recovery code file. This is not required, but if set will read a recovery code from the provided path to bootstrap a new state with.
'';
};

recoveryCodeOutPath = mkOption {
type = with types; uniq str;
description = ''
The path to the Polykey recovery code file output location.
'';
};

statePath = mkOption {
type = with types; uniq str;
default = "%h/.local/share/polykey";
description = "The path to the Polykey node state directory. Will default to `$HOME/.local/share/polykey`, but can be overwritten to a custom path.";
};
};
};
config = mkMerge [
(mkIf config.services.polykey.enable {
users.groups.polykey = {};

environment.systemPackages = [
self.outputs.packages.${buildSystem}.default
];

system.activationScripts.makeAgentPaths = ''
mkdir -p ${config.services.polykey.statePath}
chgrp -R polykey ${config.services.polykey.statePath}
chmod 770 ${config.services.polykey.statePath}
'';

systemd.services.polykey = {
description = "Polykey Agent";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
serviceConfig = {
User = "root";
Group = "polykey";
PermissionsStartOnly = true;
LoadCredential = [
"password:${config.services.polykey.passwordFilePath}"
];
ExecStartPre = ''
-${self.outputs.packages.${buildSystem}.default}/bin/polykey \
--password-file ''${CREDENTIALS_DIRECTORY}/password \
--node-path ${config.services.polykey.statePath} \
bootstrap ${lib.optionalString (config.services.polykey.recoveryCodeFilePath != "") '' -rcf ${config.services.polykey.recoveryCodeFilePath}''}\
--recovery-code-out-file ${config.services.polykey.recoveryCodeOutPath}
'';
ExecStart = ''
${self.outputs.packages.${buildSystem}.default}/bin/polykey \
--password-file ''${CREDENTIALS_DIRECTORY}/password \
--node-path ${config.services.polykey.statePath} \
agent start \
--recovery-code-out-file ${config.services.polykey.recoveryCodeOutPath}
'';
};
};
})
(mkIf config.programs.polykey.enable {
environment.systemPackages = [
self.outputs.packages.${buildSystem}.default
];

system.activationScripts.makeUserAgentPaths = ''
mkdir -p ${config.programs.polykey.statePath}
'';

systemd.user.services.polykey = {
description = "Polykey Agent";
wantedBy = [ "default.target" ];
after = [ "network.target" ];
serviceConfig = {
ExecStartPre = ''
-${self.outputs.packages.${buildSystem}.default}/bin/polykey \
--password-file ${config.programs.polykey.passwordFilePath} \
--node-path ${config.programs.polykey.statePath} \
bootstrap ${lib.optionalString (config.programs.polykey.recoveryCodeFilePath != "") '' -rcf ${config.programs.polykey.recoveryCodeFilePath}''}\
--recovery-code-out-file ${config.programs.polykey.recoveryCodeOutPath}
'';
ExecStart = ''
${self.outputs.packages.${buildSystem}.default}/bin/polykey \
--password-file ${config.programs.polykey.passwordFilePath} \
--node-path ${config.programs.polykey.statePath} \
agent start \
--recovery-code-out-file ${config.programs.polykey.recoveryCodeOutPath}
'';
};
};
})
];
};
} //
flake-utils.lib.eachSystem (builtins.attrNames systems) (targetSystem:
let
platform = builtins.elemAt systems.${targetSystem} 0;
Expand Down
10 changes: 9 additions & 1 deletion src/agent/CommandStart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import type { DeepPartial } from 'polykey/dist/types';
import type { RecoveryCode } from 'polykey/dist/keys/types';
import childProcess from 'child_process';
import process from 'process';
import fs from 'fs';
import config from 'polykey/dist/config';
import * as keysErrors from 'polykey/dist/keys/errors';
import * as polykeyEvents from 'polykey/dist/events';
Expand All @@ -28,6 +29,7 @@ class CommandStart extends CommandPolykey {
this.name('start');
this.description('Start the Polykey Agent');
this.addOption(binOptions.recoveryCodeFile);
this.addOption(binOptions.recoveryCodeOutFile);
this.addOption(binOptions.clientHost);
this.addOption(binOptions.clientPort);
this.addOption(binOptions.agentHost);
Expand Down Expand Up @@ -265,12 +267,18 @@ class CommandStart extends CommandPolykey {
type: options.format === 'json' ? 'json' : 'dict',
data: {
...statusLiveData!,
...(recoveryCodeOut != null
...(recoveryCodeOut != null && options.recoveryCodeOutFile == null
? { recoveryCode: recoveryCodeOut }
: {}),
},
}),
);
if (options.recoveryCodeOutFile != null && recoveryCodeOut != null) {
await fs.promises.writeFile(
options.recoveryCodeOutFile,
recoveryCodeOut,
);
}
});
}
}
Expand Down
26 changes: 18 additions & 8 deletions src/bootstrap/CommandBootstrap.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import process from 'process';
import fs from 'fs';
import CommandPolykey from '../CommandPolykey';
import * as binUtils from '../utils';
import * as binOptions from '../utils/options';
Expand All @@ -10,6 +11,7 @@ class CommandBootstrap extends CommandPolykey {
this.name('bootstrap');
this.description('Bootstrap Keynode State');
this.addOption(binOptions.recoveryCodeFile);
this.addOption(binOptions.recoveryCodeOutFile);
this.addOption(binOptions.fresh);
this.addOption(binOptions.privateKeyFile);
this.addOption(binOptions.passwordOpsLimit);
Expand Down Expand Up @@ -37,14 +39,22 @@ class CommandBootstrap extends CommandPolykey {
logger: this.logger,
});
this.logger.info(`Bootstrapped ${options.nodePath}`);
process.stdout.write(
binUtils.outputFormatter({
type: options.format === 'json' ? 'json' : 'dict',
data: {
recoveryCode: recoveryCodeOut,
},
}),
);

if (options.recoveryCodeOutFile == null) {
process.stdout.write(
binUtils.outputFormatter({
type: options.format === 'json' ? 'json' : 'dict',
data: {
recoveryCode: recoveryCodeOut,
},
}),
);
} else if (recoveryCodeOut != null) {
await fs.promises.writeFile(
options.recoveryCodeOutFile,
recoveryCodeOut,
);
}
});
}
}
Expand Down
8 changes: 7 additions & 1 deletion src/utils/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,12 @@ const passwordNewFile = new commander.Option(

const recoveryCodeFile = new commander.Option(
'-rcf, --recovery-code-file <path>',
'Path to Recovery Code',
'Path to a file used to load the Recovery Code from',
);

const recoveryCodeOutFile = new commander.Option(
'-rcof, --recovery-code-out-file <path>',
'Path used to write the Recovery Code if one was generated, if none was generated then this is ignored',
);

const background = new commander.Option(
Expand Down Expand Up @@ -240,6 +245,7 @@ export {
agentPort,
connConnectTime,
recoveryCodeFile,
recoveryCodeOutFile,
passwordFile,
passwordNewFile,
background,
Expand Down