From c062d8adf8a38b262cc66e6f6d51fc6b5489c592 Mon Sep 17 00:00:00 2001 From: Eric Rodrigues Pires Date: Sat, 21 Sep 2024 13:50:54 -0300 Subject: [PATCH] Verify TXT in the subdomain hierarchy This commit adds the `verify-dns-subdomains` flag, false by default. If set to true, when verifying the TXT records for a host, it will also traverse up the valid subdomains of the host and check if the fingerprint exists in any of them, to authenticate the request. --- cmd/sish.go | 1 + docs/posts/cli.md | 4 +++- go.mod | 8 ++++---- go.sum | 9 +++++++++ utils/utils.go | 36 +++++++++++++++++++++++++++++++----- 5 files changed, 48 insertions(+), 10 deletions(-) diff --git a/cmd/sish.go b/cmd/sish.go index a14aa0b..665b410 100644 --- a/cmd/sish.go +++ b/cmd/sish.go @@ -152,6 +152,7 @@ func init() { rootCmd.PersistentFlags().DurationP("authentication-key-request-timeout", "", 5*time.Second, "Duration to wait for a response from the authentication key request") rootCmd.PersistentFlags().StringP("authentication-password-request-url", "", "", "A url to validate passwords for password-based authentication.\nsish will make an HTTP POST request to this URL with a JSON body containing\nthe provided password, username, and ip address. E.g.:\n{\"password\": string, \"user\": string, \"remote_addr\": string}\nA response with status code 200 indicates approval of the password") rootCmd.PersistentFlags().DurationP("authentication-password-request-timeout", "", 5*time.Second, "Duration to wait for a response from the authentication password request") + rootCmd.PersistentFlags().BoolP("verify-dns-subdomains", "", false, "When verifying DNS information for a host, match any key fingerprint up the hierarchy of subdomains,\ninstead of only verifying for the specified host") } // initConfig initializes the configuration and loads needed diff --git a/docs/posts/cli.md b/docs/posts/cli.md index f6891a0..2998c56 100644 --- a/docs/posts/cli.md +++ b/docs/posts/cli.md @@ -1,6 +1,6 @@ --- title: CLI -description: How use sish's CLI +description: How use sish's CLI keywords: [sish, cli] --- @@ -127,6 +127,8 @@ Flags: --tcp-load-balancer Enable the TCP load balancer (multiple clients can bind the same port) --time-format string The time format to use for both HTTP and general log messages (default "2006/01/02 - 15:04:05") --verify-dns Verify DNS information for hosts and ensure it matches a connecting users sha256 key fingerprint (default true) + --verify-dns-subdomains When verifying DNS information for a host, match any key fingerprint up the hierarchy of subdomains, + instead of only verifying for the specified host --verify-ssl Verify SSL certificates made on proxied HTTP connections (default true) -v, --version version for sish -y, --whitelisted-countries string A comma separated list of whitelisted countries. Applies to HTTP, TCP, and SSH connections diff --git a/go.mod b/go.mod index 4f054d5..c6e459c 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/spf13/cobra v1.8.1 github.com/spf13/viper v1.19.0 github.com/vulcand/oxy v1.4.2 - golang.org/x/crypto v0.26.0 + golang.org/x/crypto v0.27.0 gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) @@ -79,10 +79,10 @@ require ( golang.org/x/arch v0.9.0 // indirect golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect golang.org/x/mod v0.20.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.29.0 // indirect golang.org/x/sync v0.8.0 // indirect - golang.org/x/sys v0.24.0 // indirect - golang.org/x/text v0.17.0 // indirect + golang.org/x/sys v0.25.0 // indirect + golang.org/x/text v0.18.0 // indirect golang.org/x/tools v0.24.0 // indirect google.golang.org/protobuf v1.34.2 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/go.sum b/go.sum index 49cc29c..a9dd10a 100644 --- a/go.sum +++ b/go.sum @@ -259,6 +259,8 @@ golang.org/x/crypto v0.22.0 h1:g1v0xeRhjcugydODzvb3mEM9SQ0HGp9s/nh3COQ/C30= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= +golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A= +golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -285,6 +287,8 @@ golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w= golang.org/x/net v0.24.0/go.mod h1:2Q7sJY5mzlzWjKtYUEXSlBWCdyaioyXzRB2RtU8KVE8= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo= +golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= @@ -306,11 +310,14 @@ golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.23.0 h1:F6D4vR+EHoL9/sWAWgAR1H2DcHr4PareCbAaCo1RpuU= +golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM= 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.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= @@ -318,6 +325,8 @@ golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= +golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224= +golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/utils/utils.go b/utils/utils.go index c977ffd..164f68b 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -36,6 +36,7 @@ import ( "github.com/spf13/viper" "github.com/vulcand/oxy/roundrobin" "golang.org/x/crypto/ssh" + "golang.org/x/net/publicsuffix" ) const ( @@ -684,12 +685,37 @@ func verifyDNS(addr string, sshConn *SSHConnection) (bool, string, error) { return false, "", nil } - records, err := net.LookupTXT(fmt.Sprintf("%s.%s", sishDNSPrefix, addr)) + lookupAddrs := make([]string, 1) + lookupAddrs[0] = fmt.Sprintf("%s.%s", sishDNSPrefix, addr) - for _, v := range records { - match := sshConn.SSHConn.Permissions.Extensions["pubKeyFingerprint"] == v - if match { - return match, v, err + if viper.GetBool("verify-dns-subdomains") { + topAddr, err := publicsuffix.EffectiveTLDPlusOne(addr) + if err != nil { + return false, "", err + } + if addr != topAddr { + for { + split := strings.SplitN(addr, ".", 2) + if len(split) < 2 { + return false, "", fmt.Errorf("Invalid host") + } + addr = split[1] + lookupAddrs = append(lookupAddrs, fmt.Sprintf("%s.%s", sishDNSPrefix, addr)) + if addr == topAddr { + break + } + } + } + } + + for _, lookupAddr := range lookupAddrs { + records, err := net.LookupTXT(lookupAddr) + + for _, v := range records { + match := sshConn.SSHConn.Permissions.Extensions["pubKeyFingerprint"] == v + if match { + return match, v, err + } } }