diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36f971e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bin/* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..aa7810a --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +COMMIT=$(shell git rev-parse HEAD | head -c 8)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi) +APP=gatekeeper +BUILD=-dev +REPO=ehazlett/$(APP) + +all: build + +build: + @>&2 echo " -> building ${COMMIT}" + @CGO_ENABLED=0 go build -installsuffix cgo -ldflags "-w -X github.com/$(REPO)/version.GitCommit=$(COMMIT) -X github.com/$(REPO)/version.Build=$(BUILD)" -o ./bin/$(APP) . + +clean: + @rm -rf bin + +.PHONY: build clean diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d61eecb --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/ehazlett/gatekeeper + +go 1.12 + +require ( + github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 // indirect + github.com/gliderlabs/ssh v0.2.2 + github.com/sirupsen/logrus v1.4.2 + github.com/urfave/cli v1.22.1 + golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..ac50b29 --- /dev/null +++ b/go.sum @@ -0,0 +1,34 @@ +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +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/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7 h1:0hQKqeLdqlt5iIwVOBErRisrHJAN57yOiPRQItI20fU= +golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/main.go b/main.go new file mode 100644 index 0000000..1135c16 --- /dev/null +++ b/main.go @@ -0,0 +1,61 @@ +package main + +import ( + "os" + + "github.com/ehazlett/gatekeeper/version" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +func main() { + app := cli.NewApp() + app.Name = version.Name + app.Author = "@darknet" + app.Description = version.Description + app.Version = version.BuildVersion() + app.Flags = []cli.Flag{ + cli.BoolFlag{ + Name: "debug, D", + Usage: "enable debug logging", + }, + cli.IntFlag{ + Name: "port, p", + Usage: "listen port", + Value: 2222, + }, + cli.StringFlag{ + Name: "key-dir, d", + Usage: "path to authorized public keys", + Value: "", + }, + cli.StringFlag{ + Name: "host-key, k", + Usage: "path to host key", + Value: "/etc/ssh/ssh_host_rsa_key", + }, + } + app.Before = func(cx *cli.Context) error { + if cx.Bool("debug") { + logrus.SetLevel(logrus.DebugLevel) + } + + return nil + } + app.Action = func(cx *cli.Context) error { + cfg := &ServerConfig{ + ListenPort: cx.Int("port"), + KeysPath: cx.String("key-dir"), + HostKeyPath: cx.String("host-key"), + } + srv, err := NewServer(cfg) + if err != nil { + return err + } + return srv.Run() + } + + if err := app.Run(os.Args); err != nil { + logrus.Fatal(err) + } +} diff --git a/server.go b/server.go new file mode 100644 index 0000000..c46f507 --- /dev/null +++ b/server.go @@ -0,0 +1,106 @@ +package main + +import ( + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "sync" + + "github.com/gliderlabs/ssh" + "github.com/sirupsen/logrus" + gossh "golang.org/x/crypto/ssh" +) + +type ServerConfig struct { + ListenPort int + KeysPath string + HostKeyPath string +} + +type Server struct { + cfg *ServerConfig + publicKeys []ssh.PublicKey + mu *sync.Mutex +} + +func NewServer(cfg *ServerConfig) (*Server, error) { + if cfg.ListenPort == 0 { + cfg.ListenPort = 2222 + } + return &Server{ + cfg: cfg, + mu: &sync.Mutex{}, + }, nil +} + +func (s *Server) loadKeys() error { + s.mu.Lock() + defer s.mu.Unlock() + + if s.cfg.KeysPath == "" { + return nil + } + + keys, err := ioutil.ReadDir(s.cfg.KeysPath) + if err != nil { + return err + } + + pubKeys := []ssh.PublicKey{} + for _, k := range keys { + logrus.Debugf("loading public key %s", k.Name()) + p := filepath.Join(s.cfg.KeysPath, k.Name()) + data, err := ioutil.ReadFile(p) + if err != nil { + return err + } + k, _, _, _, err := ssh.ParseAuthorizedKey(data) + if err != nil { + return err + } + pubKeys = append(pubKeys, k) + } + + s.publicKeys = pubKeys + return nil +} + +func (s *Server) Run() error { + if err := s.loadKeys(); err != nil { + return err + } + + ssh.Handle(func(s ssh.Session) { + authorizedKey := gossh.MarshalAuthorizedKey(s.PublicKey()) + io.WriteString(s, fmt.Sprintf("pub key used by %s\n", s.User())) + s.Write(authorizedKey) + }) + + pubKeyOption := ssh.PublicKeyAuth(func(ctx ssh.Context, key ssh.PublicKey) bool { + return s.isAuthorized(ctx, key) + }) + + logrus.Infof("starting ssh server on port %d", s.cfg.ListenPort) + opts := []ssh.Option{ + pubKeyOption, + } + if _, err := os.Stat(s.cfg.HostKeyPath); err == nil { + opts = append(opts, ssh.HostKeyFile(s.cfg.HostKeyPath)) + } + return ssh.ListenAndServe(fmt.Sprintf(":%d", s.cfg.ListenPort), nil, pubKeyOption) +} + +func (s *Server) isAuthorized(ctx ssh.Context, key ssh.PublicKey) bool { + for _, k := range s.publicKeys { + if ssh.KeysEqual(key, k) { + return true + } + } + logrus.WithFields(logrus.Fields{ + "user": ctx.User(), + "addr": ctx.RemoteAddr(), + }).Warn("access denied") + return false +} diff --git a/version/version.go b/version/version.go new file mode 100644 index 0000000..7578410 --- /dev/null +++ b/version/version.go @@ -0,0 +1,51 @@ +/* + Copyright 2019 Stellar Project + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in the + Software without restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included in all copies + or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package version + +import "runtime" + +var ( + // Name is the name of the application + Name = "gatekeeper" + + // Version defines the application version + Version = "0.1.0" + + // Description is the application description + Description = "darknet gatekeeper" + + // Build will be overwritten automatically by the build system + Build = "-dev" + + // GitCommit will be overwritten automatically by the build system + GitCommit = "HEAD" +) + +// BuildVersion returns the build version information including version, build and git commit +func BuildVersion() string { + return Version + Build + " (" + GitCommit + ") " + runtime.GOOS + "/" + runtime.GOARCH +} + +// FullVersion returns the build version information including version, build and git commit +func FullVersion() string { + return Name + "/" + BuildVersion() +} diff --git a/version/version_test.go b/version/version_test.go new file mode 100644 index 0000000..a9ee17e --- /dev/null +++ b/version/version_test.go @@ -0,0 +1,37 @@ +/* + Copyright 2019 Stellar Project + + Permission is hereby granted, free of charge, to any person obtaining a copy of + this software and associated documentation files (the "Software"), to deal in the + Software without restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, + and to permit persons to whom the Software is furnished to do so, subject to the + following conditions: + + The above copyright notice and this permission notice shall be included in all copies + or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, + INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE + FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +package version + +import ( + "runtime" + "testing" +) + +func TestFullVersion(t *testing.T) { + version := FullVersion() + + expected := Name + "/" + Version + Build + " (" + GitCommit + ") " + runtime.GOOS + "/" + runtime.GOARCH + + if version != expected { + t.Fatalf("invalid version returned: %s", version) + } +}