Skip to content

Commit

Permalink
Auto-merge for PR #20 via VersionBot
Browse files Browse the repository at this point in the history
Improve pty handling; Add sentry support; Kill zombies; Proper exit-status
  • Loading branch information
resin-io-versionbot[bot] authored Jun 20, 2017
2 parents 98ef8d4 + 1da2e18 commit 14d1a0b
Show file tree
Hide file tree
Showing 8 changed files with 390 additions and 190 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file
automatically by Versionist. DO NOT EDIT THIS FILE MANUALLY!
This project adheres to [Semantic Versioning](http://semver.org/).

## v1.3.0 - 2017-06-20

* Reflect command exit status in 'exit-status' request [Will Boyce]
* Kill running command if requesting channel dies [Will Boyce]
* Improve error handling [Will Boyce]
* Improve pseudo-terminal handling [Will Boyce]

## v1.2.1 - 2017-06-07

* Remove debug code from Makefile [Will Boyce]
Expand Down
6 changes: 4 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ PROJECT ?= sshproxy
PACKAGE ?= resin
EXECUTABLE ?= sshproxy
VERSION := $(shell git describe --abbrev=0 --tags)
BUILD_PLATFORMS ?= darwin/amd64 freebsd/amd64 linux/arm linux/arm64 linux/amd64 openbsd/amd64 netbsd/amd64
BUILD_PLATFORMS ?= darwin/amd64 linux/386 linux/arm linux/arm64 linux/amd64
SHASUM ?= sha256sum

all: bin/$(EXECUTABLE)
Expand All @@ -13,6 +13,7 @@ dep:
go get github.com/mitchellh/gox

lint-dep: dep
go get github.com/kisielk/errcheck
go get github.com/golang/lint/golint
go get golang.org/x/tools/cmd/goimports

Expand All @@ -21,6 +22,7 @@ lint: lint-dep
gofmt -e -l -s .
golint -set_exit_status ./...
go tool vet .
errcheck -verbose ./...

test-dep: dep
go test -i -v ./...
Expand All @@ -40,7 +42,7 @@ ifndef GITHUB_TOKEN
endif
git describe --exact-match --tags >/dev/null

git log --format='* %s' --grep=$(VERSION) --invert-grep --no-merges $(shell git describe --tag --abbrev=0 $(VERSION)^)...$(VERSION) | \
git log --format='* %s' --grep='change-type:' --regexp-ignore-case $(shell git describe --tag --abbrev=0 $(VERSION)^)...$(VERSION) | \
github-release release -u $(USERNAME) -r $(PROJECT) -t $(VERSION) -n $(VERSION) -d - || true
$(foreach FILE, $(addsuffix .tar.gz,$(addprefix build/$(EXECUTABLE)-$(VERSION)_,$(subst /,_,$(BUILD_PLATFORMS)))), \
github-release upload -u $(USERNAME) -r $(PROJECT) -t $(VERSION) -n $(notdir $(FILE)) -f $(FILE) && \
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

[![GoDoc](https://godoc.org/github.com/resin-io/sshproxy?status.svg)](https://godoc.org/github.com/resin-io/sshproxy)
[![Go Report Card](https://goreportcard.com/badge/github.com/resin-io/sshproxy)](https://goreportcard.com/report/github.com/resin-io/sshproxy)
[![CircleCI](https://circleci.com/gh/resin-io/sshproxy.png?style=shield)](https://circleci.com/gh/resin-io/sshproxy)

sshproxy is a simple ssh server library exposing an even simpler API.
Authentication is handled by providing a ServerConfig, allowing full customisation.
After authentication control is handed to the specified shell executable, with a PTY
if requested by the connecting client.
Authentication is handled by providing a ServerConfig, allowing
full customisation.
After authentication control is handed to the specified shell executable,
with a PTY if requested by the connecting client.
2 changes: 1 addition & 1 deletion circle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
build:
working_directory: /go/src/github.com/resin-io/sshproxy
docker:
- image: golang:1.8.3-stretch
- image: golang:1.8.3
steps:
- checkout
- run:
Expand Down
2 changes: 2 additions & 0 deletions resin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ they can all be set via commandline, environment or config file.
| Auth Failed Banner | `--auth-failed-banner` `-b` | `SSHPROXY_AUTH_FAILED_BANNER` | `auth-failed-banner` |
| Max Auth Tries | `--max-auth-tries` `-m` | `SSHPROXY_MAX_AUTH_TRIES` | `max-auth-tries` |
| Allow Env | `--allow-env` `-E` | `SSHPROXY_ALLOW_ENV` | `allow-env` |
| Sentry DSN | `--sentry-dsn` `-S` | `SSHPROXY_SENTRY_DSN` | `sentry-dsn` |

```
Usage of sshproxy:
Expand All @@ -39,6 +40,7 @@ Usage of sshproxy:
-d, --dir string Work dir, holds ssh keys and sshproxy config (default "/etc/sshproxy")
-m, --max-auth-tries int Maximum number of authentication attempts per connection (default 0; unlimited)
-p, --port int Port the ssh service will listen on (default 22)
-S, --sentry-dsn string Sentry DSN for error reporting
-s, --shell string Path to shell to execute post-authentication (default "shell.sh")
```

Expand Down
138 changes: 138 additions & 0 deletions resin/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/*
Copyright 2017 Resin.io
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
"bytes"
"crypto/md5"
"crypto/subtle"
"errors"
"fmt"
"strings"
"text/template"

"github.com/resin-io/pinejs-client-go"
"golang.org/x/crypto/ssh"
)

type authHandler struct {
baseURL, apiKey string
template string
rejectedSessions map[string]int
}

func newAuthHandler(baseURL, apiKey string) authHandler {
return authHandler{
baseURL: baseURL,
apiKey: apiKey,
template: "",
rejectedSessions: map[string]int{},
}
}

func (a *authHandler) getUserKeys(username string) ([]ssh.PublicKey, error) {
url := fmt.Sprintf("%s/%s", a.baseURL, "v1")
client := pinejs.NewClient(url, a.apiKey)

users := make([]map[string]interface{}, 1)
users[0] = make(map[string]interface{})
users[0]["pinejs"] = "user__has__public_key"

filter := pinejs.QueryOption{
Type: pinejs.Filter,
Content: []string{fmt.Sprintf("user/any(u:((tolower(u/username)) eq ('%s')))",
strings.ToLower(username))},
Raw: true}
fields := pinejs.QueryOption{
Type: pinejs.Select,
Content: []string{"user", "public_key"},
}
if err := client.List(&users, filter, fields); err != nil {
return nil, err
} else if len(users) == 0 {
return nil, errors.New("Invalid User")
}

keys := make([]ssh.PublicKey, 0)
for _, user := range users {
if key, _, _, _, err := ssh.ParseAuthorizedKey([]byte(user["public_key"].(string))); err == nil {
keys = append(keys, key)
}
}

return keys, nil
}

func (a *authHandler) publicKeyCallback(meta ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
keys, err := a.getUserKeys(meta.User())
if err != nil {
return nil, errors.New("Unauthorised")
}

for _, k := range keys {
if subtle.ConstantTimeCompare(k.Marshal(), key.Marshal()) == 1 {
return nil, nil
}
}

return nil, errors.New("Unauthorised")
}

func (a *authHandler) keyboardInteractiveCallback(meta ssh.ConnMetadata, client ssh.KeyboardInteractiveChallenge) (*ssh.Permissions, error) {
// check if this session has already been rejected, only send the banner once
sessionKey := string(meta.SessionID())
if _, ok := a.rejectedSessions[sessionKey]; ok {
// this operates on the assumption that `keyboard-interactive` will be attempted three times
// and then cleans up the state
a.rejectedSessions[sessionKey]++
if a.rejectedSessions[sessionKey] == 3 {
delete(a.rejectedSessions, sessionKey)
}
return nil, errors.New("Unauthorised")
}
a.rejectedSessions[sessionKey] = 1

// fetch user's keys...
keys, err := a.getUserKeys(meta.User())
if err != nil {
return nil, errors.New("Unauthorised")
}
// ...and generate their fingerprints
fingerprints := make([]string, 0)
for _, key := range keys {
hash := md5.New()
if _, err := hash.Write(key.Marshal()); err != nil {
return nil, err
}
fingerprint := fmt.Sprintf("%x", hash.Sum(nil))
fingerprints = append(fingerprints, fingerprint)
}

tmpl := template.Must(template.New("auth_failed_template").Parse(a.template))
msg := bytes.NewBuffer(nil)
// pass `user` and `fingerprints` vars to template and render
if err := tmpl.Execute(msg, map[string]interface{}{"user": meta.User(), "fingerprints": fingerprints}); err != nil {
return nil, err
}

// send the rendered template as an auth challenge with no questions
if _, err := client(meta.User(), msg.String(), nil, nil); err != nil {
return nil, err
}

return nil, errors.New("Unauthorised")
}
Loading

0 comments on commit 14d1a0b

Please sign in to comment.