support password match/ca publickey for kubernetes (#211)
* add testcase * refine testcases * fix tab * happy ql * inline timeout * add ca support for k8s
This commit is contained in:
parent
13fcc8c0a8
commit
694d09affd
15 changed files with 391 additions and 154 deletions
|
|
@ -9,7 +9,8 @@
|
|||
"features": {
|
||||
"ghcr.io/devcontainers/features/docker-outside-of-docker:1": {},
|
||||
"ghcr.io/devcontainers/features/go:1" : {},
|
||||
"ghcr.io/guiyomh/features/golangci-lint:0": {}
|
||||
"ghcr.io/guiyomh/features/golangci-lint:0": {},
|
||||
"ghcr.io/devcontainers-contrib/features/kubectl-asdf:2": {}
|
||||
},
|
||||
"customizations": {
|
||||
"vscode": {
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ services:
|
|||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- ../plugin/kubernetes/crd.yaml:/kubernetes/crd.yaml:ro
|
||||
- ./k8sworkload.yaml:/kubernetes/workload.yaml:ro
|
||||
- kubeconfig:/root/.kube
|
||||
# networks:
|
||||
# - kind
|
||||
# - default
|
||||
|
|
@ -56,19 +57,17 @@ services:
|
|||
- bash
|
||||
- -c
|
||||
- |
|
||||
# kind delete cluster -n sshpipertest
|
||||
(kind get kubeconfig -q -n sshpipertest || kind create cluster -n sshpipertest)
|
||||
docker network connect kind $$(hostname) # self contain
|
||||
docker network connect e2e_default sshpipertest-control-plane
|
||||
kind export kubeconfig -n sshpipertest --internal
|
||||
#kubectl delete po -l k8s-app=kube-dns -A
|
||||
kind load docker-image -n sshpipertest e2e_piper-imageonly
|
||||
kubectl wait --for=condition=ready pod -A --all --timeout=2m
|
||||
kubectl delete -f /kubernetes/crd.yaml
|
||||
kubectl delete -f /kubernetes/workload.yaml
|
||||
kubectl delete -f /kubernetes/crd.yaml --force
|
||||
kubectl delete -f /kubernetes/workload.yaml --force
|
||||
kubectl wait --for=delete pod --all --timeout=2m # ensure no leftover
|
||||
kubectl apply -f /kubernetes/crd.yaml
|
||||
kubectl apply -f /kubernetes/workload.yaml
|
||||
#kubectl set image deployment/sshpiper-deployment sshpiper=e2e_piper-imageonly
|
||||
kubectl wait deployment --all --for condition=Available=True
|
||||
kubectl port-forward service/sshpiper --pod-running-timeout=2m --address 0.0.0.0 2222:2222 &
|
||||
kubectl logs -f deployment/sshpiper-deployment
|
||||
|
|
@ -94,6 +93,7 @@ services:
|
|||
- sshconfig_publickey:/sshconfig_publickey
|
||||
- sshconfig_password:/sshconfig_password
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- kubeconfig:/root/.kube:ro
|
||||
command: ["./e2eentry.sh"]
|
||||
privileged: true
|
||||
working_dir: /src/e2e
|
||||
|
|
@ -119,5 +119,7 @@ volumes:
|
|||
|
||||
sshconfig_password:
|
||||
|
||||
kubeconfig:
|
||||
|
||||
networks:
|
||||
netdistract:
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
#!/bin/bash
|
||||
set -x
|
||||
|
||||
# use entrypoint.sh to generate the ssh_host_ed25519_key
|
||||
PLUGIN="dummy_badname/" bash /sshpiperd/entrypoint.sh 2>/dev/null
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ spec:
|
|||
- name: sshpiper-server-key
|
||||
mountPath: "/serverkey/"
|
||||
readOnly: true
|
||||
- name: sshpiper-files
|
||||
mountPath: "/files/"
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: sshpiper-server-key
|
||||
secret:
|
||||
|
|
@ -61,6 +64,20 @@ spec:
|
|||
items:
|
||||
- key: server_key
|
||||
path: ssh_host_ed25519_key
|
||||
- name: sshpiper-files
|
||||
configMap:
|
||||
name: sshpiper-files
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: sshpiper-files
|
||||
data:
|
||||
# htpasswd -nb htpwdfile htpasswordfile
|
||||
htpasswd: |
|
||||
htpwdfile:$apr1$qOeWE6f6$39xTQwVbAT88CMJp234wY/
|
||||
authorized_keys: |
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
|
|
@ -163,3 +180,95 @@ spec:
|
|||
private_key_secret:
|
||||
name: host-publickey-key
|
||||
ignore_hostkey: true
|
||||
|
||||
---
|
||||
apiVersion: sshpiper.com/v1beta1
|
||||
kind: Pipe
|
||||
metadata:
|
||||
name: pipe-publickey-authorized-file
|
||||
spec:
|
||||
from:
|
||||
- username: "authorizedfile"
|
||||
authorized_keys_file: /files/authorized_keys
|
||||
to:
|
||||
host: host-publickey:2222
|
||||
username: "user"
|
||||
private_key_secret:
|
||||
name: host-publickey-key
|
||||
ignore_hostkey: true
|
||||
|
||||
# htpasswd pipe to a password based sshd
|
||||
---
|
||||
apiVersion: v1
|
||||
stringData:
|
||||
password: pass
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: host-pass-secret
|
||||
type: Opaque
|
||||
---
|
||||
apiVersion: sshpiper.com/v1beta1
|
||||
kind: Pipe
|
||||
metadata:
|
||||
name: pipe-htpwd
|
||||
spec:
|
||||
from:
|
||||
- username: "htpwd"
|
||||
# htpasswd -nb htpwd htpassword
|
||||
htpasswd_data: "htpwd:$apr1$FazBEvaq$BZWaLi8Q1efsWNTKcuUgN1"
|
||||
to:
|
||||
host: host-password:2222
|
||||
username: "user"
|
||||
password_secret:
|
||||
name: host-pass-secret
|
||||
ignore_hostkey: true
|
||||
---
|
||||
apiVersion: sshpiper.com/v1beta1
|
||||
kind: Pipe
|
||||
metadata:
|
||||
name: pipe-htpwd-file
|
||||
spec:
|
||||
from:
|
||||
- username: "htpwdfile"
|
||||
htpasswd_file: /files/htpasswd
|
||||
to:
|
||||
host: host-password:2222
|
||||
username: "user"
|
||||
password_secret:
|
||||
name: host-pass-secret
|
||||
ignore_hostkey: true
|
||||
---
|
||||
|
||||
# private key with ca
|
||||
---
|
||||
apiVersion: v1
|
||||
stringData:
|
||||
ssh-privatekey: |
|
||||
-----BEGIN OPENSSH PRIVATE KEY-----
|
||||
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW
|
||||
QyNTUxOQAAACDURkx99uaw1KddraZcLpB5kfMrWwvUz2fPOoArLcpz9QAAAJC+j0+Svo9P
|
||||
kgAAAAtzc2gtZWQyNTUxOQAAACDURkx99uaw1KddraZcLpB5kfMrWwvUz2fPOoArLcpz9Q
|
||||
AAAEDcQgdh2z2r/6blq0ziJ1l6s6IAX8C+9QHfAH931cHNO9RGTH325rDUp12tplwukHmR
|
||||
8ytbC9TPZ886gCstynP1AAAADWJvbGlhbkB1YnVudHU=
|
||||
-----END OPENSSH PRIVATE KEY-----
|
||||
ssh-publickey: |
|
||||
ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQG9wZW5zc2guY29tAAAAIMIr7E6Il5fvTpixHHDCszKhlRbzXrB0fnWtxYAYdyVlAAAAINRGTH325rDUp12tplwukHmR8ytbC9TPZ886gCstynP1AAAAAAAAAAAAAAABAAAAC2s4c2UyZV90ZXN0AAAADgAAAApjbGllbnRfMTIzAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIJrzCeQ3a+m8PnA/KUmK+JAfC1tM7SG2gkGmW29nXj1nAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEDICm0JT8fakTkWTRjDtbjjMx0dUrvGvPb+Qzi2592/39CqBj8cEqZAF/DesCJhKmO+hyN4jDxTcKPhPM0y2/sH
|
||||
kind: Secret
|
||||
metadata:
|
||||
name: host-publickey-key-ca
|
||||
type: Opaque
|
||||
---
|
||||
apiVersion: sshpiper.com/v1beta1
|
||||
kind: Pipe
|
||||
metadata:
|
||||
name: pipe-publickey-ca
|
||||
spec:
|
||||
from:
|
||||
- username: "hostcapublickey"
|
||||
authorized_keys_file: /files/authorized_keys
|
||||
to:
|
||||
host: host-capublickey:2222
|
||||
username: "client_123"
|
||||
private_key_secret:
|
||||
name: host-publickey-key-ca
|
||||
ignore_hostkey: true
|
||||
|
|
@ -16,7 +16,103 @@ func TestKubernetes(t *testing.T) {
|
|||
piperaddr := piperhost + ":" + piperport
|
||||
waitForEndpointReadyWithTimeout(piperaddr, time.Minute*5)
|
||||
|
||||
t.Run("password", func(t *testing.T) {
|
||||
pubkeycases := []struct {
|
||||
title string
|
||||
user string
|
||||
}{
|
||||
{
|
||||
title: "key_pubkey_cacthall",
|
||||
user: "anyuser",
|
||||
},
|
||||
{
|
||||
title: "key_custom_field",
|
||||
user: "custom_field",
|
||||
},
|
||||
{
|
||||
title: "key_authorizedfile",
|
||||
user: "authorizedfile",
|
||||
},
|
||||
{
|
||||
title: "key_public_ca",
|
||||
user: "hostcapublickey",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range pubkeycases {
|
||||
t.Run(testcase.title, func(t *testing.T) {
|
||||
|
||||
keyfiledir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Errorf("failed to create temp key file: %v", err)
|
||||
}
|
||||
|
||||
keyfile := path.Join(keyfiledir, "key")
|
||||
|
||||
if err := os.WriteFile(keyfile, []byte(testprivatekey), 0400); err != nil {
|
||||
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 {
|
||||
t.Errorf("failed to write to authorized_keys: %v", err)
|
||||
}
|
||||
|
||||
randtext := uuid.New().String()
|
||||
targetfie := uuid.New().String()
|
||||
|
||||
c, _, _, err := runCmd(
|
||||
"ssh",
|
||||
"-v",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
"-o",
|
||||
"UserKnownHostsFile=/dev/null",
|
||||
"-p",
|
||||
piperport,
|
||||
"-l",
|
||||
testcase.user,
|
||||
"-i",
|
||||
keyfile,
|
||||
piperhost,
|
||||
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)
|
||||
|
||||
time.Sleep(time.Second) // wait for file flush
|
||||
|
||||
checkSharedFileContent(t, targetfie, randtext)
|
||||
})
|
||||
}
|
||||
|
||||
passwordcases := []struct {
|
||||
title string
|
||||
user string
|
||||
password string
|
||||
}{
|
||||
{
|
||||
title: "password",
|
||||
user: "pass",
|
||||
password: "pass",
|
||||
},
|
||||
{
|
||||
title: "password_htpwd",
|
||||
user: "htpwd",
|
||||
password: "htpassword",
|
||||
},
|
||||
{
|
||||
title: "password_htpasswd_file",
|
||||
user: "htpwdfile",
|
||||
password: "htpasswordfile",
|
||||
},
|
||||
}
|
||||
|
||||
for _, testcase := range passwordcases {
|
||||
|
||||
t.Run(testcase.title, func(t *testing.T) {
|
||||
randtext := uuid.New().String()
|
||||
targetfie := uuid.New().String()
|
||||
|
||||
|
|
@ -30,7 +126,7 @@ func TestKubernetes(t *testing.T) {
|
|||
"-p",
|
||||
piperport,
|
||||
"-l",
|
||||
"pass",
|
||||
testcase.user,
|
||||
piperhost,
|
||||
fmt.Sprintf(`sh -c "echo -n %v > /shared/%v"`, randtext, targetfie),
|
||||
)
|
||||
|
|
@ -41,106 +137,12 @@ func TestKubernetes(t *testing.T) {
|
|||
|
||||
defer killCmd(c)
|
||||
|
||||
enterPassword(stdin, stdout, "pass")
|
||||
enterPassword(stdin, stdout, testcase.password)
|
||||
|
||||
time.Sleep(time.Second) // wait for file flush
|
||||
|
||||
checkSharedFileContent(t, targetfie, randtext)
|
||||
})
|
||||
|
||||
t.Run("key", func(t *testing.T) {
|
||||
|
||||
keyfiledir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Errorf("failed to create temp key file: %v", err)
|
||||
}
|
||||
|
||||
keyfile := path.Join(keyfiledir, "key")
|
||||
|
||||
if err := os.WriteFile(keyfile, []byte(testprivatekey), 0400); err != nil {
|
||||
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 {
|
||||
t.Errorf("failed to write to authorized_keys: %v", err)
|
||||
}
|
||||
|
||||
randtext := uuid.New().String()
|
||||
targetfie := uuid.New().String()
|
||||
|
||||
c, _, _, err := runCmd(
|
||||
"ssh",
|
||||
"-v",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
"-o",
|
||||
"UserKnownHostsFile=/dev/null",
|
||||
"-p",
|
||||
piperport,
|
||||
"-l",
|
||||
"anyuser",
|
||||
"-i",
|
||||
keyfile,
|
||||
piperhost,
|
||||
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)
|
||||
|
||||
time.Sleep(time.Second) // wait for file flush
|
||||
|
||||
checkSharedFileContent(t, targetfie, randtext)
|
||||
})
|
||||
|
||||
t.Run("key_custom_field", func(t *testing.T) {
|
||||
|
||||
keyfiledir, err := os.MkdirTemp("", "")
|
||||
if err != nil {
|
||||
t.Errorf("failed to create temp key file: %v", err)
|
||||
}
|
||||
|
||||
keyfile := path.Join(keyfiledir, "key")
|
||||
|
||||
if err := os.WriteFile(keyfile, []byte(testprivatekey), 0400); err != nil {
|
||||
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 {
|
||||
t.Errorf("failed to write to authorized_keys: %v", err)
|
||||
}
|
||||
|
||||
randtext := uuid.New().String()
|
||||
targetfie := uuid.New().String()
|
||||
|
||||
c, _, _, err := runCmd(
|
||||
"ssh",
|
||||
"-v",
|
||||
"-o",
|
||||
"StrictHostKeyChecking=no",
|
||||
"-o",
|
||||
"UserKnownHostsFile=/dev/null",
|
||||
"-p",
|
||||
piperport,
|
||||
"-l",
|
||||
"custom_field",
|
||||
"-i",
|
||||
keyfile,
|
||||
piperhost,
|
||||
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)
|
||||
|
||||
time.Sleep(time.Second) // wait for file flush
|
||||
|
||||
checkSharedFileContent(t, targetfie, randtext)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ RUN apt-get update && apt-get install -y \
|
|||
gnupg \
|
||||
lsb-release
|
||||
|
||||
RUN curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.16.0/kind-linux-amd64 && \
|
||||
RUN curl -Lo ./kind https://kind.sigs.k8s.io/dl/v0.20.0/kind-linux-amd64 && \
|
||||
chmod +x ./kind && \
|
||||
mv ./kind /bin/kind
|
||||
|
||||
|
|
|
|||
|
|
@ -39,8 +39,9 @@ func waitForEndpointReady(addr string) {
|
|||
|
||||
func waitForEndpointReadyWithTimeout(addr string, timeout time.Duration) {
|
||||
now := time.Now()
|
||||
timeout = max(timeout, waitTimeout)
|
||||
for {
|
||||
if time.Since(now) > waitTimeout {
|
||||
if time.Since(now) > timeout {
|
||||
log.Panic("timeout waiting for endpoint " + addr)
|
||||
}
|
||||
|
||||
|
|
|
|||
4
go.mod
4
go.mod
|
|
@ -1,6 +1,6 @@
|
|||
module github.com/tg123/sshpiper
|
||||
|
||||
go 1.20
|
||||
go 1.21
|
||||
|
||||
replace golang.org/x/crypto => ./crypto
|
||||
|
||||
|
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/patrickmn/go-cache v2.1.0+incompatible
|
||||
github.com/pires/go-proxyproto v0.6.2
|
||||
github.com/sirupsen/logrus v1.9.3
|
||||
github.com/tg123/go-htpasswd v1.2.1
|
||||
github.com/tg123/jobobject v0.1.0
|
||||
github.com/tg123/remotesigner v0.0.1
|
||||
github.com/urfave/cli/v2 v2.25.7
|
||||
|
|
@ -29,6 +30,7 @@ require (
|
|||
)
|
||||
|
||||
require (
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 // indirect
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 // indirect
|
||||
|
|
|
|||
11
go.sum
11
go.sum
|
|
@ -8,6 +8,8 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
|
|||
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1 h1:WpB/QDNLpMw72xHJc34BNNykqSOeEJDAWkhf0u12/Jk=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.1.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962 h1:KeNholpO2xKjgaaSyd+DyQRrsQjhbSeS7qe4nEw8aQw=
|
||||
github.com/GehirnInc/crypt v0.0.0-20200316065508-bb7000b8a962/go.mod h1:kC29dT1vFpj7py2OvG1khBdQpo3kInWP+6QipLbdngo=
|
||||
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
|
||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI=
|
||||
|
|
@ -24,6 +26,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
|||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dnaeon/go-vcr v1.2.0 h1:zHCHvJYTMh1N7xnV7zf1m1GPBF9Ad0Jk/whtQ1663qI=
|
||||
github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ=
|
||||
github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8=
|
||||
github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||
github.com/docker/docker v20.10.24+incompatible h1:Ugvxm7a8+Gz6vqQYQQ2W7GYq5EUPaAiuPgIfVyI3dYE=
|
||||
|
|
@ -50,6 +53,7 @@ github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En
|
|||
github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g=
|
||||
github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI=
|
||||
github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4B2jHnOSGXyyzV8ROjYa2ojvAY6HCGYYfMoC3Ls=
|
||||
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
|
||||
|
|
@ -70,6 +74,7 @@ github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/
|
|||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1 h1:K6RDEckDVWvDI9JAJYCmNdQXq6neHJOYx3V6jnqNEec=
|
||||
github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
|
||||
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4=
|
||||
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
|
||||
|
|
@ -83,6 +88,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
|
|
@ -121,7 +127,9 @@ github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7P
|
|||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||
github.com/onsi/ginkgo/v2 v2.9.4 h1:xR7vG4IXt5RWx6FfIjyAtsoMAtnc3C/rFXBBd2AjZwE=
|
||||
github.com/onsi/ginkgo/v2 v2.9.4/go.mod h1:gCQYp2Q+kSoIj7ykSVb9nskRSsR6PUj4AiLywzIhbKM=
|
||||
github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE=
|
||||
github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
|
||||
|
|
@ -140,6 +148,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
|
|||
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
|
||||
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
|
|
@ -159,6 +168,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
|
|||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/tg123/go-htpasswd v1.2.1 h1:i4wfsX1KvvkyoMiHZzjS0VzbAPWfxzI8INcZAKtutoU=
|
||||
github.com/tg123/go-htpasswd v1.2.1/go.mod h1:erHp1B86KXdwQf1X5ZrLb7erXZnWueEQezb2dql4q58=
|
||||
github.com/tg123/jobobject v0.1.0 h1:deOWVH+SvsnFtT/M+HFhtZ7t9GMYPzYMvvF25IIMRRE=
|
||||
github.com/tg123/jobobject v0.1.0/go.mod h1:TtbMLKdmTPY6eMo4aqDXiQWv0yTfAgDt1mxv6J9pM8o=
|
||||
github.com/tg123/remotesigner v0.0.1 h1:fFCKgpBOv0nfpWOuuWMke9vECKQu6PTrm4iktDwwMMM=
|
||||
|
|
|
|||
|
|
@ -23,12 +23,16 @@ type FromSpec struct {
|
|||
Username string `json:"username"`
|
||||
UsernameRegexMatch bool `json:"username_regex_match,omitempty"`
|
||||
AuthorizedKeysData string `json:"authorized_keys_data,omitempty"`
|
||||
HtpasswdData string `json:"htpasswd_data,omitempty"`
|
||||
AuthorizedKeysFile string `json:"authorized_keys_file,omitempty"`
|
||||
HtpasswdFile string `json:"htpasswd_file,omitempty"`
|
||||
}
|
||||
|
||||
type ToSpec struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Host string `json:"host"`
|
||||
PrivateKeySecret corev1.LocalObjectReference `json:"private_key_secret,omitempty"`
|
||||
PasswordSecret corev1.LocalObjectReference `json:"password_secret,omitempty"`
|
||||
KnownHostsData string `json:"known_hosts_data,omitempty"`
|
||||
IgnoreHostkey bool `json:"ignore_hostkey,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,8 +34,14 @@ spec:
|
|||
properties:
|
||||
authorized_keys_data:
|
||||
type: string
|
||||
authorized_keys_file:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
htpasswd_data:
|
||||
type: string
|
||||
htpasswd_file:
|
||||
type: string
|
||||
username_regex_match:
|
||||
type: boolean
|
||||
required:
|
||||
|
|
@ -59,6 +65,15 @@ spec:
|
|||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
type: object
|
||||
password_secret:
|
||||
description: LocalObjectReference contains enough information
|
||||
to let you locate the referenced object inside the same namespace.
|
||||
properties:
|
||||
name:
|
||||
description: 'Name of the referent. More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
|
||||
TODO: Add other useful fields. apiVersion, kind, uid?'
|
||||
type: string
|
||||
type: object
|
||||
username:
|
||||
type: string
|
||||
required:
|
||||
|
|
|
|||
|
|
@ -1,4 +0,0 @@
|
|||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated clientset.
|
||||
package versioned
|
||||
|
|
@ -8,7 +8,6 @@ import (
|
|||
v1beta1 "github.com/tg123/sshpiper/plugin/kubernetes/apis/sshpiper/v1beta1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
|
|
@ -20,9 +19,9 @@ type FakePipes struct {
|
|||
ns string
|
||||
}
|
||||
|
||||
var pipesResource = schema.GroupVersionResource{Group: "sshpiper", Version: "v1beta1", Resource: "pipes"}
|
||||
var pipesResource = v1beta1.SchemeGroupVersion.WithResource("pipes")
|
||||
|
||||
var pipesKind = schema.GroupVersionKind{Group: "sshpiper", Version: "v1beta1", Kind: "Pipe"}
|
||||
var pipesKind = v1beta1.SchemeGroupVersion.WithKind("Pipe")
|
||||
|
||||
// Get takes name of the pipe, and returns the corresponding pipe object, and an error if there is any.
|
||||
func (c *FakePipes) Get(ctx context.Context, name string, options v1.GetOptions) (result *v1beta1.Pipe, err error) {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,13 @@ import (
|
|||
"context"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"os"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
gocache "github.com/patrickmn/go-cache"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/tg123/go-htpasswd"
|
||||
"github.com/tg123/sshpiper/libplugin"
|
||||
piperv1beta1 "github.com/tg123/sshpiper/plugin/kubernetes/apis/sshpiper/v1beta1"
|
||||
sshpiper "github.com/tg123/sshpiper/plugin/kubernetes/generated/clientset/versioned"
|
||||
|
|
@ -93,11 +96,15 @@ func (p *plugin) supportedMethods() ([]string, error) {
|
|||
|
||||
for _, pipe := range pipes {
|
||||
for _, from := range pipe.Spec.From {
|
||||
if from.AuthorizedKeysData != "" {
|
||||
if from.AuthorizedKeysData != "" || from.AuthorizedKeysFile != "" {
|
||||
set["publickey"] = true // found authorized_keys, so we support publickey
|
||||
} else {
|
||||
set["password"] = true // no authorized_keys, so we support password
|
||||
}
|
||||
|
||||
if from.HtpasswdData != "" || from.HtpasswdFile != "" {
|
||||
set["password"] = true // found htpasswd, so we support password
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -140,30 +147,92 @@ func (p *plugin) createUpstream(conn libplugin.ConnMetadata, pipe *piperv1beta1.
|
|||
IgnoreHostKey: to.IgnoreHostkey,
|
||||
}
|
||||
|
||||
if originPassword != "" {
|
||||
u.Auth = libplugin.CreatePasswordAuth([]byte(originPassword))
|
||||
p.cache.Set(conn.UniqueID(), pipe, gocache.DefaultExpiration)
|
||||
return u, nil
|
||||
}
|
||||
|
||||
if to.PrivateKeySecret.Name != "" {
|
||||
log.Debugf("mapping to %v private key using secret %v", to.Host, to.PrivateKeySecret.Name)
|
||||
secret, err := p.k8sclient.Secrets(pipe.Namespace).Get(context.Background(), to.PrivateKeySecret.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anno := pipe.GetAnnotations()
|
||||
var publicKey []byte
|
||||
var privateKey []byte
|
||||
|
||||
for _, k := range []string{"ssh-privatekey", "privatekey", anno["privatekey_field_name"]} {
|
||||
data := secret.Data[k]
|
||||
if data != nil {
|
||||
u.Auth = libplugin.CreatePrivateKeyAuth(data)
|
||||
log.Debugf("found private key in secret %v/%v", to.PrivateKeySecret.Name, k)
|
||||
privateKey = data
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for _, k := range []string{"ssh-publickey", "publickey", anno["publickey_field_name"]} {
|
||||
data := secret.Data[k]
|
||||
if data != nil {
|
||||
log.Debugf("found publickey key in secret %v/%v", to.PrivateKeySecret.Name, k)
|
||||
publicKey = data
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if privateKey != nil {
|
||||
u.Auth = libplugin.CreatePrivateKeyAuth(privateKey, publicKey)
|
||||
p.cache.Set(conn.UniqueID(), pipe, gocache.DefaultExpiration)
|
||||
return u, nil
|
||||
}
|
||||
} else if to.PasswordSecret.Name != "" {
|
||||
log.Debugf("mapping to %v password using secret %v", to.Host, to.PasswordSecret.Name)
|
||||
secret, err := p.k8sclient.Secrets(pipe.Namespace).Get(context.Background(), to.PasswordSecret.Name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
anno := pipe.GetAnnotations()
|
||||
for _, k := range []string{"password", anno["password_field_name"]} {
|
||||
data := secret.Data[k]
|
||||
if data != nil {
|
||||
log.Debugf("found password in secret %v/%v", to.PasswordSecret.Name, k)
|
||||
u.Auth = libplugin.CreatePasswordAuth(data)
|
||||
p.cache.Set(conn.UniqueID(), pipe, gocache.DefaultExpiration)
|
||||
return u, nil
|
||||
}
|
||||
}
|
||||
} else if originPassword != "" {
|
||||
log.Debugf("mapping to %v using user input password", to.Host)
|
||||
u.Auth = libplugin.CreatePasswordAuth([]byte(originPassword))
|
||||
p.cache.Set(conn.UniqueID(), pipe, gocache.DefaultExpiration)
|
||||
return u, nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no password or private key found")
|
||||
}
|
||||
|
||||
func loadStringAndFile(base64orraw string, filepath string) ([][]byte, error) {
|
||||
|
||||
all := make([][]byte, 0, 2)
|
||||
|
||||
if base64orraw != "" {
|
||||
data, err := base64.StdEncoding.DecodeString(base64orraw)
|
||||
if err != nil {
|
||||
data = []byte(base64orraw)
|
||||
}
|
||||
|
||||
all = append(all, data)
|
||||
}
|
||||
|
||||
if filepath != "" {
|
||||
data, err := os.ReadFile(filepath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
all = append(all, data)
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password string, publicKey []byte) (*libplugin.Upstream, error) {
|
||||
user := conn.User()
|
||||
|
||||
|
|
@ -185,14 +254,38 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
|
|||
}
|
||||
|
||||
if publicKey == nil && password != "" {
|
||||
return p.createUpstream(conn, pipe, password)
|
||||
}
|
||||
|
||||
rest, err := base64.StdEncoding.DecodeString(from.AuthorizedKeysData)
|
||||
pwds, err := loadStringAndFile(from.HtpasswdData, from.HtpasswdFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pwdmatched := len(pwds) == 0
|
||||
|
||||
for _, data := range pwds {
|
||||
log.Debugf("try to match password using htpasswd")
|
||||
auth, err := htpasswd.NewFromReader(bytes.NewReader(data), htpasswd.DefaultSystems, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if auth.Match(user, password) {
|
||||
pwdmatched = true
|
||||
}
|
||||
}
|
||||
|
||||
if pwdmatched {
|
||||
return p.createUpstream(conn, pipe, password)
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("try to match public using authorized key")
|
||||
pubkeydata, err := loadStringAndFile(from.AuthorizedKeysData, from.AuthorizedKeysFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, rest := range pubkeydata {
|
||||
var authedPubkey ssh.PublicKey
|
||||
for len(rest) > 0 {
|
||||
authedPubkey, _, _, rest, err = ssh.ParseAuthorizedKey(rest)
|
||||
|
|
@ -206,6 +299,7 @@ func (p *plugin) findAndCreateUpstream(conn libplugin.ConnMetadata, password str
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no matching pipe for username [%v] found", user)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ CODEGEN_PKG=${REPO_ROOT}/vendor/k8s.io/code-generator
|
|||
# k8s.io/kubernetes. The output-base is needed for the generators to output into the vendor dir
|
||||
# instead of the $GOPATH directly. For normal projects this can be dropped.
|
||||
# generators deepcopy,client,informer,lister
|
||||
chmod +x "${CODEGEN_PKG}"/generate-groups.sh
|
||||
"${CODEGEN_PKG}"/generate-groups.sh \
|
||||
chmod +x "${CODEGEN_PKG}"/kube_codegen.sh
|
||||
"${CODEGEN_PKG}"/kube_codegen.sh \
|
||||
"deepcopy,client,lister" \
|
||||
github.com/tg123/sshpiper/plugin/kubernetes/generated \
|
||||
github.com/tg123/sshpiper/plugin/kubernetes/apis \
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue