From 465a92cb8e63bfeccc35ff931ba7de875c2b9725 Mon Sep 17 00:00:00 2001 From: Jaime Pillora Date: Sun, 9 Jul 2023 21:47:40 +1000 Subject: [PATCH] Ldap authentication #369 commit 878646310b299b88dd4113a43920a66bb14891c5 Author: Jerome Doucerain Date: Fri Jul 7 19:16:27 2023 -0400 fix unreachable code Signed-off-by: Jerome Doucerain commit 3a6f13c097be58a7f526c93ce302c357e2c46185 Author: Jerome Doucerain Date: Fri Jul 7 17:22:12 2023 -0400 update README.md with features from master upstream branch Signed-off-by: Jerome Doucerain commit 3f92a77b5d660ae2c8ecaf15eebcad38bd571658 Merge: 6b87e3f ce307e5 Author: Jerome Doucerain Date: Fri Jul 7 16:45:51 2023 -0400 Merge branch 'master' of https://github.com/jpillora/chisel into LDAP-authentication commit 6b87e3fe495cd9a1983d921ce4d9b9d88c6bcfec Author: Jerome Doucerain Date: Fri Nov 25 16:52:56 2022 -0500 MD again commit b66e317f5b5b86376c70b31957f5347a359520eb Author: Jerome Doucerain Date: Fri Nov 25 16:50:20 2022 -0500 markdown adjustments commit 62920afd7d43f5ca1732bc71dcc612d3849bb2fa Merge: 3e05ec5 8f96052 Author: Jerome Doucerain Date: Fri Nov 25 16:34:44 2022 -0500 Merge branch 'LDAP-authentication' of https://github.com/jdoucerain/chisel into LDAP-authentication commit 3e05ec57faa23a0d929f992fb2ac0b6c137d2306 Author: Jerome Doucerain Date: Fri Nov 25 16:14:53 2022 -0500 markdown adjustments commit 659c06d3d11f731d8d006463e7417812979a164e Author: Jerome Doucerain Date: Fri Nov 25 16:10:52 2022 -0500 markdown adjustments commit cfb1d00000ff7a0eab6d9f705bbf807f9e85d384 Author: Jerome Doucerain Date: Fri Nov 25 15:58:32 2022 -0500 Changes as recommended by J.Pillora commit 69a8ce0fd5568167eb3f4d7e77e3a112471b85cf Author: Jerome Doucerain Date: Wed Oct 19 12:04:33 2022 -0400 a bit improved version of the password validation keeping the ability to both rely on local or LDAP authentication commit 40da15740ad033d770ecb29fd48b91649f7e326c Author: Jerome Doucerain Date: Tue Oct 18 10:59:02 2022 -0400 allow combination of both LDAP and local authentication and give ldap-config example commit 4708fe182bc78ac7144c50e79f49e8eee23d1143 Author: Jerome Doucerain Date: Wed Jun 29 19:33:27 2022 -0400 ca file debug commit 92b20ba5127855c5f32479d7b1dc0b114b0a3400 Author: Jerome Doucerain Date: Wed Jun 29 18:51:14 2022 -0400 add user in debugging log when too many entries commit ba54e8d759294cf8c860931a4395f73acb3d6307 Author: Jerome Doucerain Date: Wed Jun 29 18:43:24 2022 -0400 remove comments in ldap.go commit 505ba024c342a1876a8c1aa5fad97405344330bc Author: Jerome Doucerain Date: Wed Jun 29 18:39:23 2022 -0400 new --ldap-config in README.md commit 111b545fc7d9aaef3c7f6207b28acabca5e2fa59 Author: Jerome Doucerain Date: Wed Jun 29 18:33:57 2022 -0400 ldap authentication Signed-off-by: Jerome Doucerain commit 8f9605217b49d51cd5eed92d4643c5f3709eb187 Author: Jerome Doucerain Date: Fri Nov 25 16:14:53 2022 -0500 markdown adjustments commit 2d7c9778250ba9cb8927ecf32d95d728dcff33d5 Author: Jerome Doucerain Date: Fri Nov 25 16:10:52 2022 -0500 markdown adjustments commit 7c0e7414cb808bdf4487e6995566c5949a1cd1f9 Author: Jerome Doucerain Date: Fri Nov 25 15:58:32 2022 -0500 Changes as recommended by J.Pillora commit f7cdb96cb8525b4b62d5b4c1d5f5065476c9ef69 Author: Jerome Doucerain Date: Wed Oct 19 12:04:33 2022 -0400 a bit improved version of the password validation keeping the ability to both rely on local or LDAP authentication commit 00836b1e618d3252d719fdab4135262e9a312a60 Author: Jerome Doucerain Date: Tue Oct 18 10:59:02 2022 -0400 allow combination of both LDAP and local authentication and give ldap-config example commit 6f7bc5db73c8af98d0624d3f8ff19fef8503cecf Merge: 79c0a67 bea4540 Author: jdoucerain Date: Tue Oct 18 09:53:06 2022 -0400 Merge branch 'jpillora:master' into LDAP-authentication commit 79c0a676c531a3dfc32164e0f9b983d0187e78bd Author: Jerome Doucerain Date: Wed Jun 29 19:33:27 2022 -0400 ca file debug commit 5b77520719a10bb5e3d8313924ba8869c2a0181a Author: Jerome Doucerain Date: Wed Jun 29 18:51:14 2022 -0400 add user in debugging log when too many entries commit 6c5081371d205b17ce9d89a29e9393f991a26905 Author: Jerome Doucerain Date: Wed Jun 29 18:43:24 2022 -0400 remove comments in ldap.go commit 4bc9fc4766c33aa0dc3d5447bdfdc354cd2fdd6a Author: Jerome Doucerain Date: Wed Jun 29 18:39:23 2022 -0400 new --ldap-config in README.md commit 6c1f26968c92141e4383fd19969b14b827ce8519 Author: Jerome Doucerain Date: Wed Jun 29 18:33:57 2022 -0400 ldap authentication Signed-off-by: Jerome Doucerain --- README.md | 21 ++++++ client/client.go | 20 +++--- go.mod | 3 + go.sum | 57 ++++++++++++++++ main.go | 30 +++++++++ server/server.go | 54 ++++++++++----- share/cos/pprof.go | 1 + share/cos/signal.go | 3 +- share/cos/signal_windows.go | 3 +- share/settings/ldap.go | 130 ++++++++++++++++++++++++++++++++++++ share/settings/remote.go | 31 ++++----- share/settings/users.go | 21 ++++-- 12 files changed, 323 insertions(+), 51 deletions(-) create mode 100644 share/settings/ldap.go diff --git a/README.md b/README.md index ee3da3a..8c7b341 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,24 @@ $ chisel server --help validate client connections. The provided CA certificates will be used instead of the system roots. This is commonly used to implement mutual-TLS. + --ldap-config, a path to a JSON configuration file, which defines settings used to + connect to a remote LDAP server for authenticating users. once configured, user + passwords will be validated against the configured LDAP server. + here is an example of an ldap-config file: { + "bindDN": "CN=ldapUser,OU=Users,OU=example,DC=EXAMPLE,DC=COM", + "bindPassword": "ldapUserPassword", + "url": "example.com:636", + "baseDN": "OU=Users,OU=example,DC=EXAMPLE,DC=COM", + "filter": "(&(objectClass=person)(objectClass=user))", + "idMapTo": "sAMAccountName", + "ca": "", + "insecure": true + } + note, ldap is only used to validate password, a user or a set of users must still + be whitelisted with the --auth or --authfile flags. + + + --pid Generate pid file in current working directory -v, Enable verbose logging @@ -300,6 +318,9 @@ $ chisel client --help --hostname, Optionally set the 'Host' header (defaults to the host found in the server url). + --sni, Override the ServerName when using TLS (defaults to the + hostname). + --tls-ca, An optional root certificate bundle used to verify the chisel server. Only valid when connecting to the server with "https" or "wss". By default, the operating system CAs will be used. diff --git a/client/client.go b/client/client.go index 793e430..c96edde 100644 --- a/client/client.go +++ b/client/client.go @@ -29,7 +29,7 @@ import ( "golang.org/x/sync/errgroup" ) -//Config represents a client configuration +// Config represents a client configuration type Config struct { Fingerprint string Auth string @@ -45,7 +45,7 @@ type Config struct { Verbose bool } -//TLSConfig for a Client +// TLSConfig for a Client type TLSConfig struct { SkipVerify bool CA string @@ -54,7 +54,7 @@ type TLSConfig struct { ServerName string } -//Client represents a client instance +// Client represents a client instance type Client struct { *cio.Logger config *Config @@ -69,7 +69,7 @@ type Client struct { tunnel *tunnel.Tunnel } -//NewClient creates a new client instance +// NewClient creates a new client instance func NewClient(c *Config) (*Client, error) { //apply default scheme if !strings.HasPrefix(c.Server, "http") { @@ -105,7 +105,7 @@ func NewClient(c *Config) (*Client, error) { tlsConfig: nil, } //set default log level - client.Logger.Info = c.Verbose + client.Logger.Info = true //configure tls if u.Scheme == "wss" { tc := &tls.Config{} @@ -190,7 +190,7 @@ func NewClient(c *Config) (*Client, error) { return client, nil } -//Run starts client and blocks while connected +// Run starts client and blocks while connected func (c *Client) Run() error { ctx, cancel := context.WithCancel(context.Background()) defer cancel() @@ -221,7 +221,7 @@ func (c *Client) verifyServer(hostname string, remote net.Addr, key ssh.PublicKe return nil } -//verifyLegacyFingerprint calculates and compares legacy MD5 fingerprints +// verifyLegacyFingerprint calculates and compares legacy MD5 fingerprints func (c *Client) verifyLegacyFingerprint(key ssh.PublicKey) error { bytes := md5.Sum(key.Marshal()) strbytes := make([]string, len(bytes)) @@ -236,7 +236,7 @@ func (c *Client) verifyLegacyFingerprint(key ssh.PublicKey) error { return nil } -//Start client and does not block +// Start client and does not block func (c *Client) Start(ctx context.Context) error { ctx, cancel := context.WithCancel(ctx) c.stop = cancel @@ -293,12 +293,12 @@ func (c *Client) setProxy(u *url.URL, d *websocket.Dialer) error { return nil } -//Wait blocks while the client is running. +// Wait blocks while the client is running. func (c *Client) Wait() error { return c.eg.Wait() } -//Close manually stops the client +// Close manually stops the client func (c *Client) Close() error { if c.stop != nil { c.stop() diff --git a/go.mod b/go.mod index 265eaf3..4d1d9b0 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.20 require ( github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 github.com/fsnotify/fsnotify v1.6.0 + github.com/go-ldap/ldap/v3 v3.4.5 github.com/gorilla/websocket v1.5.0 github.com/jpillora/backoff v1.0.0 github.com/jpillora/requestlog v1.0.0 @@ -15,7 +16,9 @@ require ( ) require ( + github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 // indirect github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 // indirect + github.com/go-asn1-ber/asn1-ber v1.5.4 // indirect github.com/jpillora/ansi v1.0.3 // indirect github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/go.sum b/go.sum index 17b73e5..21bf02c 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,20 @@ +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358 h1:mFRzDkZVAjdal+s7s0MwaRv9igoPqLRdzOLzw/8Xvq8= +github.com/Azure/go-ntlmssp v0.0.0-20221128193559-754e69321358/go.mod h1:chxPXzSsl7ZWRAuOIE23GDNzjWuZquvFlgA8xmpunjU= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74 h1:Kk6a4nehpJ3UuJRqlA3JxYxBZEqCeOmATOvrbT4p9RA= +github.com/alexbrainman/sspi v0.0.0-20210105120005-909beea2cc74/go.mod h1:cEWa1LVoE5KvSD9ONXsZrj0z6KqySlCCNKHlLzbqAt4= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2 h1:axBiC50cNZOs7ygH5BgQp4N+aYrZ2DNpWZ1KG3VOSOM= github.com/andrew-d/go-termutil v0.0.0-20150726205930-009166a695a2/go.mod h1:jnzFpU88PccN/tPPhCpnNU8mZphvKxYM9lLNkd8e+os= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/go-asn1-ber/asn1-ber v1.5.4 h1:vXT6d/FNDiELJnLb6hGNa309LMsrCoYFvpwHDF0+Y1A= +github.com/go-asn1-ber/asn1-ber v1.5.4/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-ldap/ldap/v3 v3.4.5 h1:ekEKmaDrpvR2yf5Nc/DClsGG9lAmdDixe44mLzlW5r8= +github.com/go-ldap/ldap/v3 v3.4.5/go.mod h1:bMGIq3AGbytbaMwf8wdv5Phdxz0FWHTIYMSzyrYgnQs= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jpillora/ansi v1.0.3 h1:nn4Jzti0EmRfDxm7JtEs5LzCbNwd5sv+0aE+LdS9/ZQ= @@ -14,17 +25,63 @@ github.com/jpillora/requestlog v1.0.0 h1:bg++eJ74T7DYL3DlIpiwknrtfdUA9oP/M4fL+Pp github.com/jpillora/requestlog v1.0.0/go.mod h1:HTWQb7QfDc2jtHnWe2XEIEeJB7gJPnVdpNn52HXPvy8= github.com/jpillora/sizestr v1.0.0 h1:4tr0FLxs1Mtq3TnsLDV+GYUWG7Q26a6s+tV5Zfw2ygw= github.com/jpillora/sizestr v1.0.0/go.mod h1:bUhLv4ctkknatr6gR42qPxirmd5+ds1u7mzD+MZ33f0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go index eb50e6c..6e0d286 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( chserver "github.com/jpillora/chisel/server" chshare "github.com/jpillora/chisel/share" "github.com/jpillora/chisel/share/cos" + "github.com/jpillora/chisel/share/settings" ) var help = ` @@ -162,6 +163,24 @@ var serverHelp = ` holding multiple PEM encode CA certificate bundle files, which is used to validate client connections. The provided CA certificates will be used instead of the system roots. This is commonly used to implement mutual-TLS. + + --ldap-config, a path to a JSON configuration file, which defines settings used to + connect to a remote LDAP server for authenticating users. once configured, user + passwords will be validated against the configured LDAP server. + here is an example of an ldap-config file: { + "bindDN": "CN=ldapUser,OU=Users,OU=example,DC=EXAMPLE,DC=COM", + "bindPassword": "ldapUserPassword", + "url": "example.com:636", + "baseDN": "OU=Users,OU=example,DC=EXAMPLE,DC=COM", + "filter": "(&(objectClass=person)(objectClass=user))", + "idMapTo": "sAMAccountName", + "ca": "", + "insecure": true + } + note, ldap is only used to validate password, a user or a set of users must still + be whitelisted with the --auth or --authfile flags, TODO + + ` + commonHelp func server(args []string) { @@ -181,6 +200,7 @@ func server(args []string) { flags.StringVar(&config.TLS.Cert, "tls-cert", "", "") flags.Var(multiFlag{&config.TLS.Domains}, "tls-domain", "") flags.StringVar(&config.TLS.CA, "tls-ca", "", "") + ldapConfigPath := flags.String("ldap-config", "", "") host := flags.String("host", "", "") p := flags.String("p", "", "") @@ -212,6 +232,16 @@ func server(args []string) { if config.KeySeed == "" { config.KeySeed = os.Getenv("CHISEL_KEY") } + + if p := ldapConfigPath; p != nil && *p != "" { + + l, err := settings.LDAPParseConfig(*p) + if err != nil { + log.Fatal(err) + } + config.LDAPConfig = l + } + s, err := chserver.NewServer(config) if err != nil { log.Fatal(err) diff --git a/server/server.go b/server/server.go index b7df128..7d84979 100644 --- a/server/server.go +++ b/server/server.go @@ -3,6 +3,7 @@ package chserver import ( "context" "errors" + "fmt" "log" "net/http" "net/http/httputil" @@ -22,14 +23,15 @@ import ( // Config is the configuration for the chisel service type Config struct { - KeySeed string - AuthFile string - Auth string - Proxy string - Socks5 bool - Reverse bool - KeepAlive time.Duration - TLS TLSConfig + KeySeed string + AuthFile string + Auth string + Proxy string + Socks5 bool + Reverse bool + KeepAlive time.Duration + TLS TLSConfig + LDAPConfig *settings.LDAPConfig } // Server respresent a chisel service @@ -60,7 +62,7 @@ func NewServer(c *Config) (*Server, error) { sessions: settings.NewUsers(), } server.Info = true - server.users = settings.NewUserIndex(server.Logger) + server.users = settings.NewUserIndex(server.Logger, c.LDAPConfig != nil) if c.AuthFile != "" { if err := server.users.LoadUsers(c.AuthFile); err != nil { return nil, err @@ -72,6 +74,9 @@ func NewServer(c *Config) (*Server, error) { if u.Name != "" { server.users.AddUser(u) } + if c.LDAPConfig != nil && u.Pass != "" { + log.Printf("warning: LDAP auth enabled, user '%s' password will not be used", u.Name) + } } //generate private key (optionally using seed) key, err := ccrypto.GenerateKey(c.KeySeed) @@ -177,14 +182,31 @@ func (s *Server) authUser(c ssh.ConnMetadata, password []byte) (*ssh.Permissions // check the user exists and has matching password n := c.User() user, found := s.users.Get(n) - if !found || user.Pass != string(password) { - s.Debugf("Login failed for user: %s", n) - return nil, errors.New("Invalid authentication for username: %s") + if !found { + return nil, errors.New("user not found") } - // insert the user session map - // TODO this should probably have a lock on it given the map isn't thread-safe - s.sessions.Set(string(c.SessionID()), user) - return nil, nil + if string(password) == "" { + return nil, errors.New("password attempt not set") + } + if l := s.config.LDAPConfig; l != nil { + // ldap config defined, must use ldap auth + if err := settings.LDAPAuthUser(user, password, l); err != nil { + return nil, fmt.Errorf("user ldap auth failed: %w", err) + } + // ldap authentication successful + // insert the user session map + s.sessions.Set(string(c.SessionID()), user) + return nil, nil + } else if user.Pass == string(password) { + // local authentication successful + // insert the user session map + s.sessions.Set(string(c.SessionID()), user) + return nil, nil + } + if s.config.LDAPConfig.URL != "" { + } + return nil, errors.New("user auth failed") + } // AddUser adds a new user into the server user index diff --git a/share/cos/pprof.go b/share/cos/pprof.go index 6c95b7e..5b09b4b 100644 --- a/share/cos/pprof.go +++ b/share/cos/pprof.go @@ -1,3 +1,4 @@ +//go:build pprof // +build pprof package cos diff --git a/share/cos/signal.go b/share/cos/signal.go index f44e448..a26157d 100644 --- a/share/cos/signal.go +++ b/share/cos/signal.go @@ -1,4 +1,5 @@ -//+build !windows +//go:build !windows +// +build !windows package cos diff --git a/share/cos/signal_windows.go b/share/cos/signal_windows.go index 747b3d4..c72219d 100644 --- a/share/cos/signal_windows.go +++ b/share/cos/signal_windows.go @@ -1,4 +1,5 @@ -//+build windows +//go:build windows +// +build windows package cos diff --git a/share/settings/ldap.go b/share/settings/ldap.go new file mode 100644 index 0000000..6234509 --- /dev/null +++ b/share/settings/ldap.go @@ -0,0 +1,130 @@ +package settings + +import ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "log" + "os" + + "github.com/go-ldap/ldap/v3" +) + +// LDAPConfig enables LDAP auth +type LDAPConfig struct { + BindDN string `json:"bindDN"` + BindPassword string `json:"bindPassword"` + URL string `json:"url"` + BaseDN string `json:"baseDN"` + Filter string `json:"filter"` + IDMapTo string `json:"idMapTo"` + CA string `json:"ca"` + Insecure bool `json:"insecure"` +} + +// parse the LDAP config file +func LDAPParseConfig(path string) (*LDAPConfig, error) { + file, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("LDAP config file error") + } + config := &LDAPConfig{} + err = json.Unmarshal([]byte(file), config) + if err != nil { + return nil, fmt.Errorf("error occured during unmarshaling ldap config file") + } + return config, nil +} + +// authenticate a user using ldap credentials +func LDAPAuthUser(user *User, password []byte, config *LDAPConfig) error { + log.Printf("User %s to be authenticated in LDAP", user.Name) + l, err := connectTLS(config) + if err != nil { + log.Printf("Error occured during TLS connection to %s", config.URL) + return fmt.Errorf("error occured during TLS connection to %s", config.URL) + } + defer l.Close() + // Normal Bind and Search + result, err := bindAndSearch(l, config, user) + if err != nil { + log.Printf("User %s not found in LDAP", user.Name) + return fmt.Errorf("User %s not found in LDAP", user.Name) + } + userdn := result.Entries[0].DN + log.Printf("DN:%s", userdn) + if len(result.Entries) != 1 { + log.Printf("too many entries returned for user %s", user.Name) + return fmt.Errorf("too many entries returned") + } + // Bind as the user to verify their password + err = l.Bind(userdn, string(password[:])) + if err != nil { + return fmt.Errorf("bad password for user %s", user.Name) + } + + return nil +} + +// LDAP Connection with TLS +func connectTLS(ldapconfig *LDAPConfig) (*ldap.Conn, error) { + var tlsConf *tls.Config + + if ldapconfig.Insecure { + tlsConf = &tls.Config{InsecureSkipVerify: true} + } + + if ldapconfig.CA != "" { + log.Printf("CA file %s", ldapconfig.CA) + certpool := x509.NewCertPool() + CAfile, err := os.ReadFile(ldapconfig.CA) + if err != nil { + log.Printf("LDAP CA file error") + return nil, fmt.Errorf("LDAP CA file error") + } + certpool.AppendCertsFromPEM([]byte(CAfile)) + tlsConf = &tls.Config{RootCAs: certpool} + log.Printf("CA file %s loaded", ldapconfig.CA) + } + + l, err := ldap.DialTLS("tcp", ldapconfig.URL, tlsConf) + if err != nil { + log.Printf("TLS error: %s", err) + return nil, err + } + + return l, nil +} + +// Normal Bind and Search +func bindAndSearch(l *ldap.Conn, config *LDAPConfig, user *User) (*ldap.SearchResult, error) { + l.Bind(config.BindDN, config.BindPassword) + var filter string + if config.Filter != "" { + filter = fmt.Sprintf("(&(%s)(%s=%s))", config.Filter, config.IDMapTo, user.Name) + } else { + filter = fmt.Sprintf("(%s=%s)", config.IDMapTo, user.Name) + } + log.Printf("filter %s", filter) + searchReq := ldap.NewSearchRequest( + config.BaseDN, + ldap.ScopeWholeSubtree, + ldap.NeverDerefAliases, + 0, + 0, + false, + filter, + []string{"dn"}, + nil, + ) + result, err := l.Search(searchReq) + if err != nil { + log.Printf("Search Error: %s", err) + return nil, fmt.Errorf("search Error: %s", err) + } + if len(result.Entries) == 0 { + return nil, fmt.Errorf("couldn't fetch search entries") + } + return result, nil +} diff --git a/share/settings/remote.go b/share/settings/remote.go index bfe6cba..33fea90 100644 --- a/share/settings/remote.go +++ b/share/settings/remote.go @@ -48,7 +48,7 @@ func DecodeRemote(s string) (*Remote, error) { } parts := regexp.MustCompile(`(\[[^\[\]]+\]|[^\[\]:]+):?`).FindAllStringSubmatch(s, -1) if len(parts) <= 0 || len(parts) >= 5 { - return nil, errors.New("Invalid remote") + return nil, errors.New("invalid remote") } r := &Remote{Reverse: reverse} //parse from back to front, to set 'remote' fields first, @@ -82,10 +82,10 @@ func DecodeRemote(s string) (*Remote, error) { continue } if !r.Socks && (r.RemotePort == "" && r.LocalPort == "") { - return nil, errors.New("Missing ports") + return nil, errors.New("missing ports") } if !isHost(p) { - return nil, errors.New("Invalid host") + return nil, errors.New("invalid host") } if !r.Socks && r.RemoteHost == "" { r.RemoteHost = p @@ -145,15 +145,12 @@ func isPort(s string) bool { func isHost(s string) bool { _, err := url.Parse("//" + s) - if err != nil { - return false - } - return true + return err == nil } var l4Proto = regexp.MustCompile(`(?i)\/(tcp|udp)$`) -//L4Proto extacts the layer-4 protocol from the given string +// L4Proto extacts the layer-4 protocol from the given string func L4Proto(s string) (head, proto string) { if l4Proto.MatchString(s) { l := len(s) @@ -162,7 +159,7 @@ func L4Proto(s string) (head, proto string) { return s, "" } -//implement Stringer +// implement Stringer func (r Remote) String() string { sb := strings.Builder{} if r.Reverse { @@ -177,7 +174,7 @@ func (r Remote) String() string { return sb.String() } -//Encode remote to a string +// Encode remote to a string func (r Remote) Encode() string { if r.LocalPort == "" { r.LocalPort = r.RemotePort @@ -193,7 +190,7 @@ func (r Remote) Encode() string { return local + ":" + remote } -//Local is the decodable local portion +// Local is the decodable local portion func (r Remote) Local() string { if r.Stdio { return "stdio" @@ -204,7 +201,7 @@ func (r Remote) Local() string { return r.LocalHost + ":" + r.LocalPort } -//Remote is the decodable remote portion +// Remote is the decodable remote portion func (r Remote) Remote() string { if r.Socks { return "socks" @@ -215,8 +212,8 @@ func (r Remote) Remote() string { return r.RemoteHost + ":" + r.RemotePort } -//UserAddr is checked when checking if a -//user has access to a given remote +// UserAddr is checked when checking if a +// user has access to a given remote func (r Remote) UserAddr() string { if r.Reverse { return "R:" + r.LocalHost + ":" + r.LocalPort @@ -224,7 +221,7 @@ func (r Remote) UserAddr() string { return r.RemoteHost + ":" + r.RemotePort } -//CanListen checks if the port can be listened on +// CanListen checks if the port can be listened on func (r Remote) CanListen() bool { //valid protocols switch r.LocalProto { @@ -253,7 +250,7 @@ func (r Remote) CanListen() bool { type Remotes []*Remote -//Filter out forward reversed/non-reversed remotes +// Filter out forward reversed/non-reversed remotes func (rs Remotes) Reversed(reverse bool) Remotes { subset := Remotes{} for _, r := range rs { @@ -265,7 +262,7 @@ func (rs Remotes) Reversed(reverse bool) Remotes { return subset } -//Encode back into strings +// Encode back into strings func (rs Remotes) Encode() []string { s := make([]string, len(rs)) for i, r := range rs { diff --git a/share/settings/users.go b/share/settings/users.go index ea57f28..bb88613 100644 --- a/share/settings/users.go +++ b/share/settings/users.go @@ -4,7 +4,7 @@ import ( "encoding/json" "errors" "fmt" - "io/ioutil" + "os" "regexp" "sync" @@ -72,14 +72,16 @@ func (u *Users) Reset(users []*User) { type UserIndex struct { *cio.Logger *Users - configFile string + configFile string + externalAuth bool } // NewUserIndex creates a source for users -func NewUserIndex(logger *cio.Logger) *UserIndex { +func NewUserIndex(logger *cio.Logger, externalAuth bool) *UserIndex { return &UserIndex{ - Logger: logger.Fork("users"), - Users: NewUsers(), + Logger: logger.Fork("users"), + Users: NewUsers(), + externalAuth: externalAuth, } } @@ -125,7 +127,7 @@ func (u *UserIndex) loadUserIndex() error { if u.configFile == "" { return errors.New("configuration file not set") } - b, err := ioutil.ReadFile(u.configFile) + b, err := os.ReadFile(u.configFile) if err != nil { return fmt.Errorf("Failed to read auth file: %s, error: %s", u.configFile, err) } @@ -134,12 +136,16 @@ func (u *UserIndex) loadUserIndex() error { return errors.New("Invalid JSON: " + err.Error()) } users := []*User{} + passwords := false for auth, remotes := range raw { user := &User{} user.Name, user.Pass = ParseAuth(auth) if user.Name == "" { return errors.New("Invalid user:pass string") } + if user.Pass != "" { + passwords = true + } for _, r := range remotes { if r == "" || r == "*" { user.Addrs = append(user.Addrs, UserAllowAll) @@ -153,6 +159,9 @@ func (u *UserIndex) loadUserIndex() error { } users = append(users, user) } + if u.externalAuth && passwords { + u.Infof("warning: LDAP auth enabled, user passwords will not be used") + } //swap u.Reset(users) return nil