-
-
Notifications
You must be signed in to change notification settings - Fork 17.8k
ynetd: add nixos module #391184
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
ynetd: add nixos module #391184
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| config, | ||
| lib, | ||
| pkgs, | ||
| ... | ||
| }: | ||
|
|
||
| with lib; | ||
|
|
||
| let | ||
| cfg = config.programs.ynetd; | ||
| in | ||
| { | ||
| options = { | ||
| programs.ynetd = { | ||
| enable = mkEnableOption "Small server for binding programs to TCP ports"; | ||
| }; | ||
| }; | ||
|
|
||
| config = mkIf cfg.enable { | ||
| environment.systemPackages = pkgs.ynetd; | ||
| }; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,103 @@ | ||
| { | ||
| config, | ||
| lib, | ||
| pkgs, | ||
| ... | ||
| }: | ||
|
|
||
| with lib; | ||
|
|
||
| let | ||
| cfg = config.services.ynetd; | ||
|
|
||
| instanceOpts = | ||
| { name, config, ... }: | ||
| { | ||
| options = { | ||
| enable = mkEnableOption "ynetd instance ${name}"; | ||
|
|
||
| bindAddress = mkOption { | ||
| type = types.nonEmptyStr; | ||
| default = "::"; | ||
| description = "IP address to bind to"; | ||
| }; | ||
|
|
||
| port = mkOption { | ||
| type = types.port; | ||
| description = "TCP port to bind to"; | ||
| }; | ||
|
|
||
| user = mkOption { | ||
| type = types.nonEmptyStr; | ||
| default = "nobody"; | ||
| description = "Username to run the service as"; | ||
| }; | ||
|
|
||
| workingDir = mkOption { | ||
| type = types.nullOr types.path; | ||
| default = ""; | ||
| description = "Working directory for the command"; | ||
| }; | ||
|
|
||
| command = mkOption { | ||
| type = types.nonEmptyStr; | ||
| description = "Command to execute for each connection"; | ||
| }; | ||
|
|
||
| extraFlags = mkOption { | ||
| type = types.str; | ||
| default = ""; | ||
| description = "Additional flags to pass to ynetd"; | ||
| }; | ||
| }; | ||
| }; | ||
|
|
||
| # Generate systemd service for an instance | ||
| mkService = name: instanceCfg: { | ||
| description = "ynetd service for ${name}"; | ||
| after = [ "network.target" ]; | ||
| wantedBy = [ "multi-user.target" ]; | ||
|
|
||
| serviceConfig = { | ||
| ExecStart = '' | ||
| ${cfg.package}/bin/ynetd \ | ||
| -a ${instanceCfg.bindAddress} \ | ||
| -p ${toString instanceCfg.port} \ | ||
| -u ${instanceCfg.user} \ | ||
| ${optionalString (instanceCfg.workingDir != "") "-d ${instanceCfg.workingDir}"} \ | ||
| ${instanceCfg.extraFlags} \ | ||
| "${instanceCfg.command}" | ||
| ''; | ||
| User = "root"; | ||
| Restart = "always"; | ||
| RestartSec = "5s"; | ||
| }; | ||
| }; | ||
| in | ||
| { | ||
| options.services.ynetd = { | ||
| enable = mkEnableOption "ynetd service"; | ||
|
|
||
| package = mkOption { | ||
| type = types.package; | ||
| default = pkgs.ynetd; | ||
| defaultText = literalExpression "pkgs.ynetd"; | ||
| description = "The ynetd package to use (can be overridden with pkgs.ynetd.hardened for hardened version)"; | ||
| }; | ||
|
|
||
| instances = mkOption { | ||
| type = types.attrsOf (types.submodule instanceOpts); | ||
| default = { }; | ||
| description = "Attribute set of ynetd instances"; | ||
| }; | ||
| }; | ||
|
|
||
| config = mkIf cfg.enable { | ||
| # Create systemd services for each enabled instance | ||
| systemd.services = mapAttrs' ( | ||
| name: instanceCfg: nameValuePair "ynetd-${name}" (mkService name instanceCfg) | ||
| ) (filterAttrs (_: instanceCfg: instanceCfg.enable) cfg.instances); | ||
|
|
||
| environment.systemPackages = [ cfg.package ]; | ||
| }; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| import ./make-test-python.nix ( | ||
| { lib, pkgs, ... }: | ||
| let | ||
| powSolverScript = pkgs.writeTextFile { | ||
| name = "pow-solver-script.py"; | ||
| text = '' | ||
| import socket | ||
| import re | ||
| import subprocess | ||
| import sys | ||
| import time | ||
|
|
||
| def main(): | ||
| # Connect to the service | ||
| s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | ||
| s.connect(('localhost', 7000)) | ||
|
|
||
| # Get the challenge | ||
| challenge = s.recv(1024).decode('utf-8') | ||
| print(f"Received challenge: {challenge}") | ||
|
|
||
| # Extract the hex string from the challenge | ||
| hex_match = re.search(r'unhex\("([0-9a-f]+)"', challenge) | ||
| if not hex_match: | ||
| print("Could not extract hex string from challenge") | ||
| return 1 | ||
|
|
||
| hex_string = hex_match.group(1) | ||
|
|
||
| # Extract the number of zero bits | ||
| bits_match = re.search(r'(\d+) zero bits', challenge) | ||
| if not bits_match: | ||
| print("Could not extract zero bits from challenge") | ||
| return 1 | ||
|
|
||
| zero_bits = bits_match.group(1) | ||
|
|
||
| # Solve the PoW challenge | ||
| solver_cmd = ["${pkgs.ynetd.hardened}/bin/pow-solver", zero_bits, hex_string] | ||
| solution = subprocess.check_output(solver_cmd).decode('utf-8').strip() | ||
| print(f"Solution: {solution}") | ||
|
|
||
| # Send the solution | ||
| s.sendall((solution + '\n').encode('utf-8')) | ||
|
|
||
| # Wait a bit for the service to process the solution | ||
| time.sleep(0.5) | ||
|
|
||
| # Send our test message | ||
| test_message = "Hello from hardened!\n" | ||
| s.sendall(test_message.encode('utf-8')) | ||
|
|
||
| # Get the response | ||
| response = s.recv(1024).decode('utf-8') | ||
| print(f"Response: {response}") | ||
|
|
||
| # Check if our message was echoed back | ||
| if "Hello from hardened!" in response: | ||
| print("Test passed!") | ||
| return 0 | ||
| else: | ||
| print("Test failed!") | ||
| return 1 | ||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
| ''; | ||
| executable = true; | ||
| destination = "/bin/pow-solver-script.py"; | ||
| }; | ||
| in | ||
| { | ||
| name = "ynetd-hardened"; | ||
|
|
||
| nodes.machine = { | ||
| services.ynetd = { | ||
| enable = true; | ||
| package = pkgs.ynetd.hardened; | ||
|
|
||
| instances = { | ||
| echo = { | ||
| enable = true; | ||
| port = 7000; | ||
| command = "${pkgs.coreutils}/bin/cat"; | ||
| extraFlags = "-pow 5"; | ||
| }; | ||
| }; | ||
| }; | ||
|
|
||
| # Include our solver script | ||
| environment.systemPackages = [ powSolverScript ]; | ||
| }; | ||
|
|
||
| testScript = '' | ||
| start_all() | ||
|
|
||
| machine.wait_for_unit("ynetd-echo.service") | ||
| machine.wait_for_open_port(7000) | ||
|
|
||
| # Run the test script | ||
| test_result = machine.succeed("${pkgs.python3}/bin/python3 ${powSolverScript}/bin/pow-solver-script.py") | ||
| print(f"Hardened test result: {test_result}") | ||
|
|
||
| # Verify the test passed | ||
| assert "Test passed!" in test_result, "Hardened echo service test failed" | ||
|
|
||
| # Verify service is active | ||
| machine.succeed("systemctl is-active ynetd-echo.service") | ||
| ''; | ||
|
|
||
| meta.maintainers = [ lib.maintainers.haylin ]; | ||
| } | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import ./make-test-python.nix ( | ||
| { lib, pkgs, ... }: | ||
| { | ||
| name = "ynetd"; | ||
|
|
||
| nodes.machine = { | ||
| services.ynetd = { | ||
| enable = true; | ||
|
|
||
| instances = { | ||
| echo = { | ||
| enable = true; | ||
| port = 7000; | ||
| command = "${pkgs.coreutils}/bin/cat"; | ||
| }; | ||
|
|
||
| shell = { | ||
| enable = true; | ||
| port = 7001; | ||
| command = "${pkgs.bash}/bin/bash -i"; | ||
| extraFlags = "-se y"; | ||
| }; | ||
| }; | ||
| }; | ||
| }; | ||
|
|
||
| testScript = '' | ||
| start_all() | ||
|
|
||
| machine.wait_for_unit("ynetd-echo.service") | ||
| machine.wait_for_unit("ynetd-shell.service") | ||
|
|
||
| machine.wait_for_open_port(7000) | ||
| machine.wait_for_open_port(7001) | ||
|
|
||
| # Test echo service | ||
| echo_output = machine.succeed("echo 'Hello, world!' | nc -w 1 localhost 7000") | ||
| assert "Hello, world!" in echo_output, f"Echo service failed: {echo_output}" | ||
|
|
||
| # Test security | ||
| machine.succeed(""" | ||
| cat > /tmp/shell_commands.txt << 'EOF' | ||
| id -u -n | ||
| cat /etc/shadow | ||
| exit | ||
| EOF | ||
| """) | ||
|
|
||
| # Send commands to the shell service and capture output | ||
| security_output = machine.succeed("cat /tmp/shell_commands.txt | nc -w 2 localhost 7001") | ||
| print(f"Security check output: {security_output}") | ||
|
|
||
| # Verify running as nobody | ||
| assert "nobody" in security_output, "Service not running as nobody user" | ||
|
|
||
| # Verify cannot read /etc/shadow | ||
| assert "Permission denied" in security_output, "Expected permission denied when trying to read /etc/shadow" | ||
|
|
||
| # Verify services are active | ||
| machine.succeed("systemctl is-active ynetd-echo.service") | ||
| machine.succeed("systemctl is-active ynetd-shell.service") | ||
| ''; | ||
|
|
||
| meta.maintainers = [ lib.maintainers.haylin ]; | ||
| } | ||
| ) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be root? Can a systemd user or whatever not be used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is the bit I feel a little uneasy regarding, I have a little explanation in the second paragraph of my PR, like we could set it to a less powerful user, but then for cgroups I believe it would need
CAP_SYS_ADMINat which point it's basically root just under another name.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if I go through and model more of the flags I can dynamically apply some of the CAP for the unhardened ynetd. I'll go through and see how this looks