From 1308b729c1f1991a8635943f9f31d58f725de05b Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Wed, 22 May 2019 12:23:32 +0200 Subject: [PATCH 1/2] Add secrets options to service and composition --- .../modules/composition/docker-compose.nix | 40 ++++++++++++ .../service/docker-compose-service.nix | 61 ++++++++++++++++++- tests/arion-test/default.nix | 9 +++ tests/testcases/secrets/arion-compose.nix | 17 ++++++ tests/testcases/secrets/arion-pkgs.nix | 2 + tests/testcases/secrets/foo.key | 1 + 6 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 tests/testcases/secrets/arion-compose.nix create mode 100644 tests/testcases/secrets/arion-pkgs.nix create mode 100644 tests/testcases/secrets/foo.key diff --git a/src/nix/modules/composition/docker-compose.nix b/src/nix/modules/composition/docker-compose.nix index 95855ff..999ff40 100644 --- a/src/nix/modules/composition/docker-compose.nix +++ b/src/nix/modules/composition/docker-compose.nix @@ -11,8 +11,36 @@ */ { pkgs, lib, config, ... }: let + cfg = config.docker-compose; + inherit (lib) mkOption optionalAttrs mapAttrs; + inherit (lib.types) submodule attrsOf nullOr either str path bool; evalService = name: modules: pkgs.callPackage ../../eval-service.nix {} { inherit name modules; inherit (config) host; }; + dockerComposeRef = fragment: + ''See Docker Compose#${fragment}''; + + secretType = submodule { + options = { + file = mkOption { + type = either path str; + description = '' + Sets the secret's value to this file. + + ${dockerComposeRef "secrets"} + ''; + }; + external = mkOption { + type = bool; + default = false; + description = '' + Whether the value of this secret is set via other means. + + ${dockerComposeRef "secrets"} + ''; + }; + }; + }; + in { options = { @@ -42,6 +70,11 @@ in description = "Attribute set of evaluated service configurations."; readOnly = true; }; + docker-compose.secrets = lib.mkOption { + type = attrsOf secretType; + description = dockerComposeRef "secrets"; + default = {}; + }; }; config = { build.dockerComposeYaml = pkgs.writeText "docker-compose.yaml" config.build.dockerComposeYamlText; @@ -52,6 +85,13 @@ in version = "3.4"; services = lib.mapAttrs (k: c: c.config.build.service) config.docker-compose.evaluatedServices; x-arion = config.docker-compose.extended; + } // optionalAttrs (cfg.secrets != {}) { + secrets = mapAttrs (_k: s: optionalAttrs (s.external != false) { + inherit (s) external; + } // optionalAttrs (s.file != null) { + file = toString s.file; + } + ) cfg.secrets; }; }; } diff --git a/src/nix/modules/service/docker-compose-service.nix b/src/nix/modules/service/docker-compose-service.nix index 54dae9f..d40c6f7 100644 --- a/src/nix/modules/service/docker-compose-service.nix +++ b/src/nix/modules/service/docker-compose-service.nix @@ -7,9 +7,11 @@ { pkgs, lib, config, ... }: let - inherit (lib) mkOption types; + inherit (lib) mkOption types mapAttrs mapAttrsToList; inherit (types) listOf nullOr attrsOf str either int bool; + cfg = config.service; + link = url: text: ''${text}''; dockerComposeRef = fragment: @@ -23,6 +25,48 @@ let cap_add = lib.attrNames (lib.filterAttrs (name: value: value == true) config.service.capabilities); cap_drop = lib.attrNames (lib.filterAttrs (name: value: value == false) config.service.capabilities); + serviceSecretType = types.submodule { + options = { + source = mkOption { + type = nullOr str; + default = null; + description = dockerComposeRef "secrets"; + }; + uid = mkOption { + type = nullOr (either str int); + default = null; + description = dockerComposeRef "secrets"; + }; + gid = mkOption { + type = nullOr (either str int); + default = null; + description = dockerComposeRef "secrets"; + }; + mode = mkOption { + type = nullOr str; + # default = "0444"; + default = null; + description = '' + The default value of is usually 0444. This option may not be supported + when not deploying to a Swarm. + + ${dockerComposeRef "secrets"} + ''; + }; + }; + }; + secrets = mapAttrsToList (k: s: { + target = k; + } //lib.optionalAttrs (s.source != null) { + inherit (s) source; + } // lib.optionalAttrs (s.uid != null) { + inherit (s) uid; + } // lib.optionalAttrs (s.gid != null) { + inherit (s) gid; + } // lib.optionalAttrs (s.mode != null) { + inherit (s) mode; + }) cfg.secrets; + in { options = { @@ -197,6 +241,19 @@ in } ''; }; + service.secrets = mkOption { + type = attrsOf serviceSecretType; + default = {}; + description = dockerComposeRef "secrets"; + example = { + redis_secret = { + source = "web_cache_redis_secret"; + uid = 123; + gid = 123; + mode = "0440"; + }; + }; + }; }; config.build.service = { @@ -242,6 +299,8 @@ in inherit (config.service) restart; } // lib.optionalAttrs (config.service.stop_signal != null) { inherit (config.service) stop_signal; + } // lib.optionalAttrs (secrets != null) { + inherit secrets; } // lib.optionalAttrs (config.service.tmpfs != []) { inherit (config.service) tmpfs; } // lib.optionalAttrs (config.service.tty != null) { diff --git a/tests/arion-test/default.nix b/tests/arion-test/default.nix index 1020a1b..40f2ccd 100644 --- a/tests/arion-test/default.nix +++ b/tests/arion-test/default.nix @@ -34,6 +34,7 @@ in (preEval [ ../../examples/minimal/arion-compose.nix ]).config.build.dockerComposeYaml (preEval [ ../../examples/full-nixos/arion-compose.nix ]).config.build.dockerComposeYaml (preEval [ ../../examples/nixos-unit/arion-compose.nix ]).config.build.dockerComposeYaml + (preEval [ ../testcases/secrets/arion-compose.nix ]).config.build.dockerComposeYaml pkgs.stdenv ]; }; @@ -63,5 +64,13 @@ in $machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work"); $machine->waitUntilFails("curl localhost:8000"); }; + + subtest "secrets", sub { + $machine->succeed("cp -r ${../testcases/secrets} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d"); + $machine->waitUntilSucceeds("curl localhost:8000"); + $machine->succeed("test qux = \"\$(curl localhost:8000/foo.txt)\""); + $machine->succeed("(cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down) && rm -rf work"); + $machine->waitUntilFails("curl localhost:8000"); + }; ''; } diff --git a/tests/testcases/secrets/arion-compose.nix b/tests/testcases/secrets/arion-compose.nix new file mode 100644 index 0000000..dc530eb --- /dev/null +++ b/tests/testcases/secrets/arion-compose.nix @@ -0,0 +1,17 @@ +{ + docker-compose.services.webserver = { pkgs, ... }: { + nixos.useSystemd = true; + nixos.configuration.boot.tmpOnTmpfs = true; + nixos.configuration.services.nginx.enable = true; + + # Please don't do this + nixos.configuration.services.nginx.virtualHosts.localhost.root = "/run/secrets"; + + service.useHostStore = true; + service.ports = [ + "8000:80" # host:container + ]; + service.secrets."foo.txt".source = "foo"; + }; + docker-compose.secrets.foo.file = ./foo.key; +} diff --git a/tests/testcases/secrets/arion-pkgs.nix b/tests/testcases/secrets/arion-pkgs.nix new file mode 100644 index 0000000..d13a605 --- /dev/null +++ b/tests/testcases/secrets/arion-pkgs.nix @@ -0,0 +1,2 @@ +# Instead of pinning Nixpkgs, we can opt to use the one in NIX_PATH +import {} diff --git a/tests/testcases/secrets/foo.key b/tests/testcases/secrets/foo.key new file mode 100644 index 0000000..78df5b0 --- /dev/null +++ b/tests/testcases/secrets/foo.key @@ -0,0 +1 @@ +qux \ No newline at end of file From 894cb2815bb3a7a26922483d8844542501a9be18 Mon Sep 17 00:00:00 2001 From: Robert Hensing Date: Sun, 2 Jun 2019 12:50:30 +0200 Subject: [PATCH 2/2] Small test improvements --- tests/arion-test/default.nix | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/arion-test/default.nix b/tests/arion-test/default.nix index 40f2ccd..be869fb 100644 --- a/tests/arion-test/default.nix +++ b/tests/arion-test/default.nix @@ -12,11 +12,10 @@ in machine = { pkgs, lib, ... }: { environment.systemPackages = [ pkgs.arion - pkgs.docker-compose ]; virtualisation.docker.enable = true; - # no caches, because no internet + # no caches, because the test cannot access the internet nix.binaryCaches = lib.mkForce []; # FIXME: Sandbox seems broken with current version of NixOS test @@ -29,14 +28,14 @@ in virtualisation.writableStore = true; virtualisation.pathsInNixDB = [ - # Pre-build the image because we don't want to build the world - # in the vm. + # Pre-build the image because we don't want to build the world in the vm. (preEval [ ../../examples/minimal/arion-compose.nix ]).config.build.dockerComposeYaml (preEval [ ../../examples/full-nixos/arion-compose.nix ]).config.build.dockerComposeYaml (preEval [ ../../examples/nixos-unit/arion-compose.nix ]).config.build.dockerComposeYaml (preEval [ ../testcases/secrets/arion-compose.nix ]).config.build.dockerComposeYaml pkgs.stdenv ]; + virtualisation.memorySize = 4096; #MB }; testScript = '' $machine->fail("curl localhost:8000"); @@ -45,7 +44,7 @@ in subtest "minimal", sub { $machine->succeed("cp -r ${../../examples/minimal} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d"); $machine->waitUntilSucceeds("curl localhost:8000"); - $machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work"); + $machine->succeed("(cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down) && rm -rf work"); $machine->waitUntilFails("curl localhost:8000"); }; @@ -54,14 +53,14 @@ in $machine->waitUntilSucceeds("curl localhost:8000"); # Also test exec with defaultExec $machine->succeed("cd work && export NIX_PATH=nixpkgs='${pkgs.path}' && (echo 'nix run -f ~/h/arion arion -c arion exec webserver'; echo 'target=world; echo Hello \$target'; echo exit) | script /dev/null | grep 'Hello world'"); - $machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work"); + $machine->succeed("(cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down) && rm -rf work"); $machine->waitUntilFails("curl localhost:8000"); }; subtest "nixos-unit", sub { $machine->succeed("cp -r ${../../examples/nixos-unit} work && cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion up -d"); $machine->waitUntilSucceeds("curl localhost:8000"); - $machine->succeed("cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down && rm -rf work"); + $machine->succeed("(cd work && NIX_PATH=nixpkgs='${pkgs.path}' arion down) && rm -rf work"); $machine->waitUntilFails("curl localhost:8000"); };