Skip to content

Commit

Permalink
Add password authentication request validation (Eugeny#323)
Browse files Browse the repository at this point in the history
This adds a password-based authentication mechanism under the option
`--authentication-password-request-url`, which functions similarly
to `--authentication-key-request-url`, but takes a password
instead of a public key.

Closes Eugeny#322
  • Loading branch information
EpicEric authored Sep 17, 2024
1 parent b06d594 commit 23ccc59
Show file tree
Hide file tree
Showing 4 changed files with 57 additions and 3 deletions.
2 changes: 2 additions & 0 deletions cmd/sish.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ func init() {
rootCmd.PersistentFlags().DurationP("authentication-keys-directory-watch-interval", "", 200*time.Millisecond, "The interval to poll for filesystem changes for SSH keys")
rootCmd.PersistentFlags().DurationP("https-certificate-directory-watch-interval", "", 200*time.Millisecond, "The interval to poll for filesystem changes for HTTPS certificates")
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")
}

// initConfig initializes the configuration and loads needed
Expand Down
2 changes: 2 additions & 0 deletions config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ authentication-key-request-url: ""
authentication-keys-directory: deploy/pubkeys/
authentication-keys-directory-watch-interval: 200ms
authentication-password: ""
authentication-password-request-url: ""
authentication-password-request-timeout: 5s
banned-aliases: ""
banned-countries: ""
banned-ips: ""
Expand Down
8 changes: 6 additions & 2 deletions docs/posts/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ description: How use sish's CLI
keywords: [sish, cli]
---

```text
```text
sish is a command line utility that implements an SSH server that can handle HTTP(S)/WS(S)/TCP multiplexing, forwarding and load balancing.
It can handle multiple vhosting and reverse tunneling endpoints for a large number of clients.
Expand All @@ -31,6 +30,12 @@ Flags:
from the authentication list (default "deploy/pubkeys/")
--authentication-keys-directory-watch-interval duration The interval to poll for filesystem changes for SSH keys (default 200ms)
-u, --authentication-password string Password to use for SSH server password authentication
--authentication-password-request-timeout duration Duration to wait for a response from the authentication password request (default 5s)
--authentication-password-request-url string A url to validate passwords for password-based authentication.
sish will make an HTTP POST request to this URL with a JSON body containing
the provided password, username, and ip address. E.g.:
{"password": string, "user": string, "remote_addr": string}
A response with status code 200 indicates approval of the password
--banned-aliases string A comma separated list of banned aliases that users are unable to bind
-o, --banned-countries string A comma separated list of banned countries. Applies to HTTP, TCP, and SSH connections
-x, --banned-ips string A comma separated list of banned ips that are unable to access the service. Applies to HTTP, TCP, and SSH connections
Expand Down Expand Up @@ -127,4 +132,3 @@ Flags:
-y, --whitelisted-countries string A comma separated list of whitelisted countries. Applies to HTTP, TCP, and SSH connections
-w, --whitelisted-ips string A comma separated list of whitelisted ips. Applies to HTTP, TCP, and SSH connections
```
```
48 changes: 47 additions & 1 deletion utils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,18 @@ func GetSSHConfig() *ssh.ServerConfig {
return nil, nil
}

// Allow validation of passwords via a sub-request to another service
authUrl := viper.GetString("authentication-password-request-url")
if authUrl != "" {
validKey, err := checkAuthenticationPasswordRequest(authUrl, password, c.RemoteAddr(), c.User())
if err != nil {
log.Printf("Error calling authentication password URL %s: %s\n", authUrl, err)
}
if validKey {
return nil, nil
}
}

return nil, fmt.Errorf("password doesn't match")
},
PublicKeyCallback: func(c ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
Expand Down Expand Up @@ -490,7 +502,7 @@ func GetSSHConfig() *ssh.ServerConfig {
if authUrl != "" {
validKey, err := checkAuthenticationKeyRequest(authUrl, authKey, c.RemoteAddr(), c.User())
if err != nil {
log.Printf("Error calling authentication URL %s: %s\n", authUrl, err)
log.Printf("Error calling authentication key URL %s: %s\n", authUrl, err)
}
if validKey {
permssionsData := &ssh.Permissions{
Expand Down Expand Up @@ -547,6 +559,40 @@ func checkAuthenticationKeyRequest(authUrl string, authKey []byte, addr net.Addr
return true, nil
}

// checkAuthenticationPasswordRequest makes an HTTP POST request to the specified url with
// the provided user-password pair to validate whether it should be accepted.
func checkAuthenticationPasswordRequest(authUrl string, password []byte, addr net.Addr, user string) (bool, error) {
parsedUrl, err := url.ParseRequestURI(authUrl)
if err != nil {
return false, fmt.Errorf("error parsing url %s", err)
}

c := &http.Client{
Timeout: viper.GetDuration("authentication-password-request-timeout"),
}
urlS := parsedUrl.String()
reqBodyMap := map[string]string{
"password": string(password),
"remote_addr": addr.String(),
"user": user,
}
reqBody, err := json.Marshal(reqBodyMap)
if err != nil {
return false, fmt.Errorf("error jsonifying request body")
}
res, err := c.Post(urlS, "application/json", bytes.NewBuffer(reqBody))
if err != nil {
return false, err
}

if res.StatusCode != http.StatusOK {
log.Printf("Password rejected by auth service: %s with status %d", urlS, res.StatusCode)
return false, nil
}

return true, nil
}

// generatePrivateKey creates a new ed25519 private key to be used by the
// the SSH server as the host key.
func generatePrivateKey(passphrase string) []byte {
Expand Down

0 comments on commit 23ccc59

Please sign in to comment.