Merge pull request #123841 from hercules-ci/podman-socket
nixos/podman: Add docker socket supportlaunchpad/nixpkgs/master
commit
774fe1878b
@ -0,0 +1,34 @@ |
||||
{ config, lib, pkg, ... }: |
||||
let |
||||
inherit (lib) |
||||
mkOption |
||||
types |
||||
; |
||||
|
||||
cfg = config.virtualisation.podman.networkSocket; |
||||
|
||||
in |
||||
{ |
||||
options.virtualisation.podman.networkSocket = { |
||||
server = mkOption { |
||||
type = types.enum [ "ghostunnel" ]; |
||||
}; |
||||
}; |
||||
|
||||
config = { |
||||
|
||||
services.ghostunnel = lib.mkIf (cfg.enable && cfg.server == "ghostunnel") { |
||||
enable = true; |
||||
servers."podman-socket" = { |
||||
inherit (cfg.tls) cert key cacert; |
||||
listen = "${cfg.listenAddress}:${toString cfg.port}"; |
||||
target = "unix:/run/podman/podman.sock"; |
||||
allowAll = lib.mkDefault true; |
||||
}; |
||||
}; |
||||
systemd.services.ghostunnel-server-podman-socket.serviceConfig.SupplementaryGroups = ["podman"]; |
||||
|
||||
}; |
||||
|
||||
meta.maintainers = lib.teams.podman.members ++ [ lib.maintainers.roberth ]; |
||||
} |
@ -0,0 +1,91 @@ |
||||
{ config, lib, pkg, ... }: |
||||
let |
||||
inherit (lib) |
||||
mkOption |
||||
types |
||||
; |
||||
|
||||
cfg = config.virtualisation.podman.networkSocket; |
||||
|
||||
in |
||||
{ |
||||
options.virtualisation.podman.networkSocket = { |
||||
enable = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = '' |
||||
Make the Podman and Docker compatibility API available over the network |
||||
with TLS client certificate authentication. |
||||
|
||||
This allows Docker clients to connect with the equivalents of the Docker |
||||
CLI <code>-H</code> and <code>--tls*</code> family of options. |
||||
|
||||
For certificate setup, see https://docs.docker.com/engine/security/protect-access/ |
||||
|
||||
This option is independent of <xref linkend="opt-virtualisation.podman.dockerSocket.enable"/>. |
||||
''; |
||||
}; |
||||
|
||||
server = mkOption { |
||||
type = types.enum []; |
||||
description = '' |
||||
Choice of TLS proxy server. |
||||
''; |
||||
example = "ghostunnel"; |
||||
}; |
||||
|
||||
openFirewall = mkOption { |
||||
type = types.bool; |
||||
default = false; |
||||
description = '' |
||||
Whether to open the port in the firewall. |
||||
''; |
||||
}; |
||||
|
||||
tls.cacert = mkOption { |
||||
type = types.path; |
||||
description = '' |
||||
Path to CA certificate to use for client authentication. |
||||
''; |
||||
}; |
||||
|
||||
tls.cert = mkOption { |
||||
type = types.path; |
||||
description = '' |
||||
Path to certificate describing the server. |
||||
''; |
||||
}; |
||||
|
||||
tls.key = mkOption { |
||||
type = types.path; |
||||
description = '' |
||||
Path to the private key corresponding to the server certificate. |
||||
|
||||
Use a string for this setting. Otherwise it will be copied to the Nix |
||||
store first, where it is readable by any system process. |
||||
''; |
||||
}; |
||||
|
||||
port = mkOption { |
||||
type = types.port; |
||||
default = 2376; |
||||
description = '' |
||||
TCP port number for receiving TLS connections. |
||||
''; |
||||
}; |
||||
listenAddress = mkOption { |
||||
type = types.str; |
||||
default = "0.0.0.0"; |
||||
description = '' |
||||
Interface address for receiving TLS connections. |
||||
''; |
||||
}; |
||||
}; |
||||
|
||||
config = { |
||||
networking.firewall.allowedTCPPorts = |
||||
lib.optional (cfg.enable && cfg.openFirewall) cfg.port; |
||||
}; |
||||
|
||||
meta.maintainers = lib.teams.podman.members ++ [ lib.maintainers.roberth ]; |
||||
} |
@ -0,0 +1,150 @@ |
||||
/* |
||||
This test runs podman as a backend for the Docker CLI. |
||||
*/ |
||||
import ./make-test-python.nix ( |
||||
{ pkgs, lib, ... }: |
||||
|
||||
let gen-ca = pkgs.writeScript "gen-ca" '' |
||||
# Create CA |
||||
PATH="${pkgs.openssl}/bin:$PATH" |
||||
openssl genrsa -out ca-key.pem 4096 |
||||
openssl req -new -x509 -days 365 -key ca-key.pem -sha256 -subj '/C=NL/ST=Zuid-Holland/L=The Hague/O=Stevige Balken en Planken B.V./OU=OpSec/CN=Certificate Authority' -out ca.pem |
||||
|
||||
# Create service |
||||
openssl genrsa -out podman-key.pem 4096 |
||||
openssl req -subj '/CN=podman' -sha256 -new -key podman-key.pem -out service.csr |
||||
echo subjectAltName = DNS:podman,IP:127.0.0.1 >> extfile.cnf |
||||
echo extendedKeyUsage = serverAuth >> extfile.cnf |
||||
openssl x509 -req -days 365 -sha256 -in service.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out podman-cert.pem -extfile extfile.cnf |
||||
|
||||
# Create client |
||||
openssl genrsa -out client-key.pem 4096 |
||||
openssl req -subj '/CN=client' -new -key client-key.pem -out client.csr |
||||
echo extendedKeyUsage = clientAuth > extfile-client.cnf |
||||
openssl x509 -req -days 365 -sha256 -in client.csr -CA ca.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -extfile extfile-client.cnf |
||||
|
||||
# Create CA 2 |
||||
PATH="${pkgs.openssl}/bin:$PATH" |
||||
openssl genrsa -out ca-2-key.pem 4096 |
||||
openssl req -new -x509 -days 365 -key ca-2-key.pem -sha256 -subj '/C=NL/ST=Zuid-Holland/L=The Hague/O=Stevige Balken en Planken B.V./OU=OpSec/CN=Certificate Authority' -out ca-2.pem |
||||
|
||||
# Create client signed by CA 2 |
||||
openssl genrsa -out client-2-key.pem 4096 |
||||
openssl req -subj '/CN=client' -new -key client-2-key.pem -out client-2.csr |
||||
echo extendedKeyUsage = clientAuth > extfile-client.cnf |
||||
openssl x509 -req -days 365 -sha256 -in client-2.csr -CA ca-2.pem -CAkey ca-2-key.pem -CAcreateserial -out client-2-cert.pem -extfile extfile-client.cnf |
||||
|
||||
''; |
||||
in |
||||
{ |
||||
name = "podman-tls-ghostunnel"; |
||||
meta = { |
||||
maintainers = lib.teams.podman.members ++ [ lib.maintainers.roberth ]; |
||||
}; |
||||
|
||||
nodes = { |
||||
podman = |
||||
{ pkgs, ... }: |
||||
{ |
||||
virtualisation.podman.enable = true; |
||||
virtualisation.podman.dockerSocket.enable = true; |
||||
virtualisation.podman.networkSocket = { |
||||
enable = true; |
||||
openFirewall = true; |
||||
server = "ghostunnel"; |
||||
tls.cert = "/root/podman-cert.pem"; |
||||
tls.key = "/root/podman-key.pem"; |
||||
tls.cacert = "/root/ca.pem"; |
||||
}; |
||||
|
||||
environment.systemPackages = [ |
||||
pkgs.docker-client |
||||
]; |
||||
|
||||
users.users.alice = { |
||||
isNormalUser = true; |
||||
home = "/home/alice"; |
||||
description = "Alice Foobar"; |
||||
extraGroups = ["podman"]; |
||||
}; |
||||
|
||||
}; |
||||
|
||||
client = { ... }: { |
||||
environment.systemPackages = [ |
||||
# Installs the docker _client_ only |
||||
# Normally, you'd want `virtualisation.docker.enable = true;`. |
||||
pkgs.docker-client |
||||
]; |
||||
environment.variables.DOCKER_HOST = "podman:2376"; |
||||
environment.variables.DOCKER_TLS_VERIFY = "1"; |
||||
}; |
||||
}; |
||||
|
||||
testScript = '' |
||||
import shlex |
||||
|
||||
|
||||
def su_cmd(user, cmd): |
||||
cmd = shlex.quote(cmd) |
||||
return f"su {user} -l -c {cmd}" |
||||
|
||||
def cmd(command): |
||||
print(f"+{command}") |
||||
r = os.system(command) |
||||
if r != 0: |
||||
raise Exception(f"Command {command} failed with exit code {r}") |
||||
|
||||
start_all() |
||||
cmd("${gen-ca}") |
||||
|
||||
podman.copy_from_host("ca.pem", "/root/ca.pem") |
||||
podman.copy_from_host("podman-cert.pem", "/root/podman-cert.pem") |
||||
podman.copy_from_host("podman-key.pem", "/root/podman-key.pem") |
||||
|
||||
client.copy_from_host("ca.pem", "/root/.docker/ca.pem") |
||||
# client.copy_from_host("podman-cert.pem", "/root/podman-cert.pem") |
||||
client.copy_from_host("client-cert.pem", "/root/.docker/cert.pem") |
||||
client.copy_from_host("client-key.pem", "/root/.docker/key.pem") |
||||
|
||||
# TODO (ghostunnel): add file watchers so the restart isn't necessary |
||||
podman.succeed("systemctl reset-failed && systemctl restart ghostunnel-server-podman-socket.service") |
||||
|
||||
podman.wait_for_unit("sockets.target") |
||||
podman.wait_for_unit("ghostunnel-server-podman-socket.service") |
||||
|
||||
with subtest("Create default network"): |
||||
podman.succeed("docker network create default") |
||||
|
||||
with subtest("Root docker cli also works"): |
||||
podman.succeed("docker version") |
||||
|
||||
with subtest("A podman member can also still use the docker cli"): |
||||
podman.succeed(su_cmd("alice", "docker version")) |
||||
|
||||
with subtest("Run container remotely via docker cli"): |
||||
client.succeed("docker version") |
||||
|
||||
# via socket would be nicer |
||||
podman.succeed("tar cv --files-from /dev/null | podman import - scratchimg") |
||||
|
||||
client.succeed( |
||||
"docker run -d --name=sleeping -v /nix/store:/nix/store -v /run/current-system/sw/bin:/bin scratchimg /bin/sleep 10" |
||||
) |
||||
client.succeed("docker ps | grep sleeping") |
||||
podman.succeed("docker ps | grep sleeping") |
||||
client.succeed("docker stop sleeping") |
||||
client.succeed("docker rm sleeping") |
||||
|
||||
with subtest("Clients without cert will be denied"): |
||||
client.succeed("rm /root/.docker/{cert,key}.pem") |
||||
client.fail("docker version") |
||||
|
||||
with subtest("Clients with wrong cert will be denied"): |
||||
client.copy_from_host("client-2-cert.pem", "/root/.docker/cert.pem") |
||||
client.copy_from_host("client-2-key.pem", "/root/.docker/key.pem") |
||||
client.fail("docker version") |
||||
|
||||
''; |
||||
} |
||||
) |
Loading…
Reference in new issue