From e96e3f6a3a240655af5d2c66620a72f3ebd8665c Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Sun, 16 Feb 2025 23:19:41 -0800 Subject: [PATCH] Customize sshd with sshd config.d (#525) * Update OpenSSH server image version and add no penalties configuration * Refactor SSH configuration paths and update test scripts for public key handling * Update Docker Compose volumes to remove read-only flag for shared directory * Remove end-to-end test files and related Docker configuration for CA public cert authentication --- CONTRIBUTING.md | 2 +- e2e/ca_test.go | 60 ------------------ e2e/cahost/Dockerfile | 19 ------ e2e/cahost/sshd_config | 23 ------- e2e/docker-compose.yml | 31 +++++----- e2e/docker_test.go | 2 +- e2e/e2eentry.sh | 5 -- e2e/k8sworkload.yaml | 4 +- e2e/kubernetes_test.go | 2 +- e2e/sshdconfig/no_penalties.conf | 1 + e2e/sshdconfig/trusted-ca.conf | 1 + e2e/{cahost/ca => sshdconfig/trusted-ca.key} | 0 .../ca.pub => sshdconfig/trusted-ca.pub} | 0 e2e/testplugin/testcaplugin/main.go | 61 ------------------- e2e/workingdir_test.go | 2 +- e2e/yaml_test.go | 2 +- 16 files changed, 24 insertions(+), 191 deletions(-) delete mode 100644 e2e/ca_test.go delete mode 100644 e2e/cahost/Dockerfile delete mode 100644 e2e/cahost/sshd_config create mode 100644 e2e/sshdconfig/no_penalties.conf create mode 100644 e2e/sshdconfig/trusted-ca.conf rename e2e/{cahost/ca => sshdconfig/trusted-ca.key} (100%) rename e2e/{cahost/ca.pub => sshdconfig/trusted-ca.pub} (100%) delete mode 100644 e2e/testplugin/testcaplugin/main.go diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 460aa1d4..9e3aae88 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -33,7 +33,7 @@ SSHPIPERD_DEBUG=1 docker-compose up --force-recreate --build -d you will have two sshd: * `host-password:2222`: a password only sshd server (user: `user`, password: `pass`) - * `host-publickey:2222`: a public key only sshd server (put your public key in `/sshconfig_publickey/.config/authorized_keys`) + * `host-publickey:2222`: a public key only sshd server (put your public key in `/publickey_authorized_keys/authorized_keys`) more settings: diff --git a/e2e/ca_test.go b/e2e/ca_test.go deleted file mode 100644 index af44e0dc..00000000 --- a/e2e/ca_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package e2e_test - -import ( - "fmt" - "testing" - "time" - - "github.com/google/uuid" -) - -func TestCa(t *testing.T) { - - piperaddr, piperport := nextAvailablePiperAddress() - - piper, _, _, err := runCmd("/sshpiperd/sshpiperd", - "-p", - piperport, - "/sshpiperd/plugins/testcaplugin", - "--target", - "host-capublickey:2222", - ) - - if err != nil { - t.Errorf("failed to run sshpiperd: %v", err) - } - - defer killCmd(piper) - - waitForEndpointReady(piperaddr) - - randtext := uuid.New().String() - targetfie := uuid.New().String() - - c, stdin, stdout, err := runCmd( - "ssh", - "-v", - "-o", - "StrictHostKeyChecking=no", - "-o", - "UserKnownHostsFile=/dev/null", - "-p", - piperport, - "-l", - "client_123", - "127.0.0.1", - fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie), - ) - - if err != nil { - t.Errorf("failed to ssh to piper-fixed, %v", err) - } - - defer killCmd(c) - - enterPassword(stdin, stdout, "pass") - - time.Sleep(time.Second) // wait for file flush - - checkSharedFileContent(t, targetfie, randtext) -} diff --git a/e2e/cahost/Dockerfile b/e2e/cahost/Dockerfile deleted file mode 100644 index f98ef801..00000000 --- a/e2e/cahost/Dockerfile +++ /dev/null @@ -1,19 +0,0 @@ -FROM debian:bookworm-slim - -EXPOSE 2222 - -ENV DEBIAN_FRONTEND=noninteractive - -COPY ca.pub /etc/ssh/trusted-ca.pub - -RUN apt -y update \ - && apt -y install --no-install-recommends --no-install-suggests openssh-server \ - && mkdir -p /run/sshd \ - && adduser --disabled-password --gecos "" client_123 \ - && passwd -d client_123 \ - && rm -rf /var/lib/apt/lists/* - -COPY sshd_config /etc/ssh/sshd_config - -ENTRYPOINT ["/usr/sbin/sshd", "-D", "-e", "-f", "/etc/ssh/sshd_config"] - diff --git a/e2e/cahost/sshd_config b/e2e/cahost/sshd_config deleted file mode 100644 index 67a6b043..00000000 --- a/e2e/cahost/sshd_config +++ /dev/null @@ -1,23 +0,0 @@ -Port 2222 - -# Trust our CA -TrustedUserCAKeys /etc/ssh/trusted-ca.pub - -# Allow pubkey -PubkeyAuthentication yes - -# Don't allow passwords -PasswordAuthentication no -# Don't allow host auth -HostbasedAuthentication no -# No challenge response -ChallengeResponseAuthentication no - -# Allow client to pass locale environment variables -AcceptEnv LANG LC_* - -# override default of no subsystems -Subsystem sftp /usr/lib/openssh/sftp-server - -# Change this if you have issues -LogLevel INFO diff --git a/e2e/docker-compose.yml b/e2e/docker-compose.yml index b3b373a7..0ee9d688 100644 --- a/e2e/docker-compose.yml +++ b/e2e/docker-compose.yml @@ -2,7 +2,7 @@ version: '3.4' services: host-password: - image: lscr.io/linuxserver/openssh-server:9.7_p1-r4-ls184 + image: lscr.io/linuxserver/openssh-server:9.9_p1-r2-ls190 environment: - PASSWORD_ACCESS=true - USER_PASSWORD=pass @@ -15,13 +15,13 @@ services: - sshpiper.network=e2e_default volumes: - shared:/shared - - sshconfig_password:/config + - ./sshdconfig/no_penalties.conf:/config/sshd/sshd_config.d/no_penalties.conf:ro networks: - default - netdistract host-publickey: - image: lscr.io/linuxserver/openssh-server:9.7_p1-r4-ls184 + image: lscr.io/linuxserver/openssh-server:9.9_p1-r2-ls190 environment: - USER_NAME=user - LOG_STDOUT=true @@ -31,18 +31,20 @@ services: - sshpiper.authorized_keys=c3NoLWVkMjU1MTkgQUFBQUMzTnphQzFsWkRJMU5URTVBQUFBSU5SR1RIMzI1ckRVcDEydHBsd3VrSG1SOHl0YkM5VFBaODg2Z0NzdHluUDEgdGVzdEB0ZXN0Cg== - sshpiper.private_key=LS0tLS1CRUdJTiBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0KYjNCbGJuTnphQzFyWlhrdGRqRUFBQUFBQkc1dmJtVUFBQUFFYm05dVpRQUFBQUFBQUFBQkFBQUFNd0FBQUF0emMyZ3RaVwpReU5UVXhPUUFBQUNEVVJreDk5dWF3MUtkZHJhWmNMcEI1a2ZNcld3dlV6MmZQT29BckxjcHo5UUFBQUpDK2owK1N2bzlQCmtnQUFBQXR6YzJndFpXUXlOVFV4T1FBQUFDRFVSa3g5OXVhdzFLZGRyYVpjTHBCNWtmTXJXd3ZVejJmUE9vQXJMY3B6OVEKQUFBRURjUWdkaDJ6MnIvNmJscTB6aUoxbDZzNklBWDhDKzlRSGZBSDkzMWNITk85UkdUSDMyNXJEVXAxMnRwbHd1a0htUgo4eXRiQzlUUFo4ODZnQ3N0eW5QMUFBQUFEV0p2YkdsaGJrQjFZblZ1ZEhVPQotLS0tLUVORCBPUEVOU1NIIFBSSVZBVEUgS0VZLS0tLS0K volumes: - - shared:/shared - - sshconfig_publickey:/config + - shared:/shared + - publickey_authorized_keys:/config/.ssh/ + - ./sshdconfig/no_penalties.conf:/config/sshd/sshd_config.d/no_penalties.conf:ro host-capublickey: - build: ./cahost - labels: - - sshpiper.port=2222 - - sshpiper.network=e2e_default + image: lscr.io/linuxserver/openssh-server:9.9_p1-r2-ls190 + environment: + - USER_NAME=ca_user + - LOG_STDOUT=true volumes: - shared:/shared - networks: - - default + - ./sshdconfig/no_penalties.conf:/config/sshd/sshd_config.d/no_penalties.conf:ro + - ./sshdconfig/trusted-ca.conf:/config/sshd/sshd_config.d/trusted-ca.conf:ro + - ./sshdconfig/trusted-ca.pub:/config/sshd/trusted-ca.pub:ro host-k8s-proxy: @@ -95,8 +97,7 @@ services: volumes: - ..:/src - shared:/shared - - sshconfig_publickey:/sshconfig_publickey - - sshconfig_password:/sshconfig_password + - publickey_authorized_keys:/publickey_authorized_keys - /var/run/docker.sock:/var/run/docker.sock - kubeconfig:/root/.kube:ro command: ["./e2eentry.sh"] @@ -121,9 +122,7 @@ volumes: type: tmpfs device: tmpfs - sshconfig_publickey: - - sshconfig_password: + publickey_authorized_keys: kubeconfig: diff --git a/e2e/docker_test.go b/e2e/docker_test.go index 327b2afe..9f6c150b 100644 --- a/e2e/docker_test.go +++ b/e2e/docker_test.go @@ -72,7 +72,7 @@ func TestDocker(t *testing.T) { t.Errorf("failed to write to test key: %v", err) } - if err := os.WriteFile("/sshconfig_publickey/.ssh/authorized_keys", []byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1`), 0400); err != nil { + if err := os.WriteFile("/publickey_authorized_keys/authorized_keys", []byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1`), 0400); err != nil { t.Errorf("failed to write to authorized_keys: %v", err) } diff --git a/e2e/e2eentry.sh b/e2e/e2eentry.sh index 0bf16299..0aeff65c 100755 --- a/e2e/e2eentry.sh +++ b/e2e/e2eentry.sh @@ -4,11 +4,6 @@ set -x # use entrypoint.sh to generate the ssh_host_ed25519_key PLUGIN="dummy_badname/" bash /sshpiperd/entrypoint.sh 2>/dev/null -# Create an ssh key pair and then sign the public key with the ca cert -chmod 600 cahost/ca -ssh-keygen -t ssh-ed25519 -f /etc/ssh/ssh_user -N "" -ssh-keygen -s cahost/ca -I ssh_user -n client_123 /etc/ssh/ssh_user.pub - if [ "${SSHPIPERD_DEBUG}" == "1" ]; then echo "enter debug on hold mode" echo "run [docker exec -ti e2e_testrunner_1 bash] to run to attach" diff --git a/e2e/k8sworkload.yaml b/e2e/k8sworkload.yaml index 96febb27..f849968e 100644 --- a/e2e/k8sworkload.yaml +++ b/e2e/k8sworkload.yaml @@ -255,7 +255,7 @@ stringData: 8ytbC9TPZ886gCstynP1AAAADWJvbGlhbkB1YnVudHU= -----END OPENSSH PRIVATE KEY----- ssh-publickey-cert: | - ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMIr7E6Il5fvTpixHHDCszKhlRbzXrB0fnWtxYAYdyVlAAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1AAAAAAAAAAAAAAABAAAAC2s4c2UyZV90ZXN0AAAADgAAAApjbGllbnRfMTIzAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJrzCeQ3a+m8PnA/KUmK+JAfC1tM7SG2gkGmW29nXj1nAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDICm0JT8fakTkWTRjDtbjjMx0dUrvGvPb+Qzi2592/39CqBj8cEqZAF/DesCJhKmO+hyN4jDxTcKPhPM0y2/sH + ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIAf6qglkclnZFuSIlW4ClXcwq+SCYJn7rCYcUpbVVaKrAAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1AAAAAAAAAAAAAAABAAAACHNzaF91c2VyAAAACwAAAAdjYV91c2VyAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJrzCeQ3a+m8PnA/KUmK+JAfC1tM7SG2gkGmW29nXj1nAAAAUwAAAAtzc2gtZWQyNTUxOQAAAECM8mKjWLhTKmhj4sb6r0CVaTz/vO8oy1o/7OzmQyUMMa1ex4mo+HB3RHa5eUGOdcFAJu6O8r6GBEah+O0maH8A kind: Secret metadata: name: host-publickey-key-ca @@ -271,7 +271,7 @@ spec: authorized_keys_file: /files/authorized_keys to: host: host-capublickey:2222 - username: "client_123" + username: "ca_user" private_key_secret: name: host-publickey-key-ca ignore_hostkey: true diff --git a/e2e/kubernetes_test.go b/e2e/kubernetes_test.go index d900100f..f4d5437b 100644 --- a/e2e/kubernetes_test.go +++ b/e2e/kubernetes_test.go @@ -56,7 +56,7 @@ func TestKubernetes(t *testing.T) { t.Errorf("failed to write to test key: %v", err) } - if err := os.WriteFile("/sshconfig_publickey/.ssh/authorized_keys", []byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1`), 0400); err != nil { + if err := os.WriteFile("/publickey_authorized_keys/authorized_keys", []byte(`ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1`), 0400); err != nil { t.Errorf("failed to write to authorized_keys: %v", err) } diff --git a/e2e/sshdconfig/no_penalties.conf b/e2e/sshdconfig/no_penalties.conf new file mode 100644 index 00000000..c19821eb --- /dev/null +++ b/e2e/sshdconfig/no_penalties.conf @@ -0,0 +1 @@ +PerSourcePenaltyExemptList 0.0.0.0/0 \ No newline at end of file diff --git a/e2e/sshdconfig/trusted-ca.conf b/e2e/sshdconfig/trusted-ca.conf new file mode 100644 index 00000000..13655709 --- /dev/null +++ b/e2e/sshdconfig/trusted-ca.conf @@ -0,0 +1 @@ +TrustedUserCAKeys /config/sshd/trusted-ca.pub diff --git a/e2e/cahost/ca b/e2e/sshdconfig/trusted-ca.key similarity index 100% rename from e2e/cahost/ca rename to e2e/sshdconfig/trusted-ca.key diff --git a/e2e/cahost/ca.pub b/e2e/sshdconfig/trusted-ca.pub similarity index 100% rename from e2e/cahost/ca.pub rename to e2e/sshdconfig/trusted-ca.pub diff --git a/e2e/testplugin/testcaplugin/main.go b/e2e/testplugin/testcaplugin/main.go deleted file mode 100644 index 19c07bb9..00000000 --- a/e2e/testplugin/testcaplugin/main.go +++ /dev/null @@ -1,61 +0,0 @@ -//go:build full || e2e - -package main - -import ( - "fmt" - "os" - - "github.com/tg123/sshpiper/libplugin" - "github.com/urfave/cli/v2" -) - -func main() { - libplugin.CreateAndRunPluginTemplate(&libplugin.PluginTemplate{ - Name: "testcaplugin", - Usage: "Plugin to test ca public cert auth", - Flags: []cli.Flag{ - &cli.StringFlag{ - Name: "target", - Usage: "target ssh endpoint address", - EnvVars: []string{"SSHPIPERD_FIXED_TARGET"}, - Required: true, - }, - }, - CreateConfig: func(c *cli.Context) (*libplugin.SshPiperPluginConfig, error) { - target := c.String("target") - - host, port, err := libplugin.SplitHostPortForSSH(target) - if err != nil { - return nil, err - } - - return &libplugin.SshPiperPluginConfig{ - PasswordCallback: func(conn libplugin.ConnMetadata, password []byte) (*libplugin.Upstream, error) { - if string(password) != "pass" { - return nil, fmt.Errorf("Password is incorrect") - } - - // Load the private key - signer, err := os.ReadFile("/etc/ssh/ssh_user") - if err != nil { - return nil, fmt.Errorf("unable to read private key: %v", err) - } - - // Load the public ca certificate - capublickey, err := os.ReadFile("/etc/ssh/ssh_user-cert.pub") - if err != nil { - return nil, fmt.Errorf("unable to read ca cert: %v", err) - } - - return &libplugin.Upstream{ - Host: host, - Port: int32(port), - IgnoreHostKey: true, - Auth: libplugin.CreatePrivateKeyAuth(signer, capublickey), - }, nil - }, - }, nil - }, - }) -} diff --git a/e2e/workingdir_test.go b/e2e/workingdir_test.go index 03f3358a..6f9a0263 100644 --- a/e2e/workingdir_test.go +++ b/e2e/workingdir_test.go @@ -188,7 +188,7 @@ func TestWorkingDirectory(t *testing.T) { if err := runCmdAndWait( "/bin/cp", path.Join(userdir, "id_rsa.pub"), - "/sshconfig_publickey/.ssh/authorized_keys", + "/publickey_authorized_keys/authorized_keys", ); err != nil { t.Errorf("failed to copy public key: %v", err) } diff --git a/e2e/yaml_test.go b/e2e/yaml_test.go index b3d07755..7c32cfac 100644 --- a/e2e/yaml_test.go +++ b/e2e/yaml_test.go @@ -129,7 +129,7 @@ func TestYaml(t *testing.T) { if err := runCmdAndWait( "/bin/cp", path.Join(yamldir, "id_rsa.pub"), - "/sshconfig_publickey/.ssh/authorized_keys", + "/publickey_authorized_keys/authorized_keys", ); err != nil { t.Errorf("failed to copy public key: %v", err) }