Browse Source

Made IDPs pluggable

master
parent
commit
bae902620d
10 changed files with 248 additions and 154 deletions
  1. +2
    -0
      .gitignore
  2. +7
    -4
      cmd/config.go
  3. +27
    -10
      cmd/gate/main.go
  4. +10
    -140
      pkg/core/identity.go
  5. +34
    -0
      pkg/core/identity/config.go
  6. +1
    -0
      pkg/core/identity/ldap.go
  7. +151
    -0
      pkg/core/identity/system.go
  8. +3
    -0
      pkg/core/service.go
  9. +5
    -0
      pkg/net/secureshell/server.go
  10. +8
    -0
      pkg/net/web/server.go

+ 2
- 0
.gitignore View File

@ -12,3 +12,5 @@
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Log files are ignored
*.log

+ 7
- 4
cmd/config.go View File

@ -4,10 +4,10 @@ import (
"io/ioutil"
"os"
"maze.io/gate/pkg/core"
"github.com/hashicorp/hcl"
"maze.io/gate/pkg/core"
"maze.io/gate/pkg/core/identity"
"maze.io/gate/pkg/core/logger"
"maze.io/gate/pkg/net/secureshell"
"maze.io/gate/pkg/net/web"
@ -18,7 +18,7 @@ type Config struct {
SecureShell *secureshell.Server `hcl:"sshd"`
Web *web.Server `hcl:"httpd"`
Log logger.Config `hcl:"log"`
IDP interface{}
Identity *identity.Config `hcl:"identity"`
}
func LoadConfig(name string) (*Config, error) {
@ -46,10 +46,13 @@ func (config *Config) Logger() *logger.Logger {
return log
}
func (config *Config) IdentityProvider() (core.IdentityProvider, error) {
return config.Identity.Provider()
}
// Services returns all configured services.
func (config *Config) Services() (httpd *web.Server, services []core.Service) {
if config.SecureShell != nil {
config.SecureShell.IdentityProvider = core.DefaultIdentityProvider
services = append(services, config.SecureShell)
}
if config.Web != nil {


+ 27
- 10
cmd/gate/main.go View File

@ -2,6 +2,7 @@ package main
import (
"flag"
"os"
"maze.io/gate/pkg/net/web"
@ -16,30 +17,46 @@ func main() {
)
flag.Parse()
log := logger.New(os.Stdout)
config, err := cmd.LoadConfig(*configFlag)
if err != nil {
panic(err)
log.WithError(err).WithField("name", *configFlag).Fatal("failed to load configuration")
}
log = config.Logger()
provider, err := config.IdentityProvider()
if err != nil {
log.WithError(err).WithField("name", *configFlag).Fatal("failed to load identity provider")
}
if err = provider.Setup(log); err != nil {
log.WithError(err).Fatal("failed to setup identity provider")
}
httpd, services := config.Services()
run(config.Logger(), httpd, services)
run(log, httpd, services, provider)
}
func run(log *logger.Logger, httpd *web.Server, services []core.Service) {
func run(log *logger.Logger, httpd *web.Server, services []core.Service, provider core.IdentityProvider) {
log.Info("services starting")
errors := make(chan error, 16)
for i, service := range services {
log := log.WithField(logger.Component, service.Component())
log.Info("starting")
if err := service.Start(log, errors); err != nil {
if err := service.Setup(provider); err != nil {
log.WithError(err).Error("service setup failed")
} else if err := service.Start(log, errors); err != nil {
log.WithError(err).Error("service start failed")
for j := i; j >= 0; j-- {
log.WithField(logger.Component, services[j].Component()).Info("closing")
_ = services[j].Close()
}
return
} else {
log.Info("started")
continue
}
// Service failed to start, rewind the other services.
for j := i; j >= 0; j-- {
log.WithField(logger.Component, services[j].Component()).Info("closing")
_ = services[j].Close()
}
log.Info("started")
return
}
if httpd != nil {


+ 10
- 140
pkg/core/identity.go View File

@ -1,26 +1,22 @@
package core
import (
"bytes"
"errors"
"io/ioutil"
"os/user"
"path/filepath"
"strconv"
"golang.org/x/crypto/ssh"
"maze.io/gate/pkg/core/logger"
"maze.io/gate/pkg/util/compact"
)
var (
// DefaultIdentityProvider uses the system authentication layer.
DefaultIdentityProvider systemIdentityProvider
"maze.io/gate/pkg/util/compact"
)
// IdentityProvider provides user and groups.
type IdentityProvider interface {
// Setup the IDP with a logger.
Setup(logger *logger.Logger) error
// LookupUser resolves a user by login name.
LookupUser(name string) (User, error)
// LookupGroup resolves a group by name.
LookupGroup(name string) (Group, error)
// PasswordCallback, if non-nil, is called when a obj attempts to authenticate using a password.
@ -37,6 +33,7 @@ type IdentityProvider interface {
PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error)
}
// User entity.
type User interface {
ID() compact.ID
Login() string
@ -44,136 +41,9 @@ type User interface {
Groups() []Group
}
// Group entity.
type Group interface {
ID() compact.ID
Name() string
Members() []User
}
type systemIdentityProvider struct{}
func (idp systemIdentityProvider) LookupUser(name string) (User, error) {
u, err := user.Lookup(name)
if err != nil {
return nil, err
}
return systemUser{idp, u}, nil
}
func (idp systemIdentityProvider) LookupGroup(name string) (Group, error) {
if _, err := strconv.Atoi(name); err == nil {
g, err := user.LookupGroupId(name)
if err != nil {
return nil, err
}
return systemGroup{idp, g}, nil
}
g, err := user.LookupGroup(name)
if err != nil {
return nil, err
}
return systemGroup{idp, g}, nil
}
func (idp systemIdentityProvider) PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
user, err := idp.LookupUser(conn.User())
if err != nil {
return nil, err
}
_ = user
return nil, errors.New("password not implemented")
}
type systemKey struct {
ssh.PublicKey
Options []string
}
func (idp systemIdentityProvider) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
log := logger.Default.WithFields(logger.Fields{
logger.User: conn.User(),
"key": ssh.FingerprintSHA256(key),
"key_type": key.Type(),
}).WithSourceAddr(conn.RemoteAddr())
log.Debug("public key authentication")
user, err := idp.LookupUser(conn.User())
if err != nil {
return nil, err
}
name := filepath.Join(user.(systemUser).obj.HomeDir, ".ssh", "authorized_keys")
b, err := ioutil.ReadFile(name)
if err != nil {
return nil, err
}
marshaled := key.Marshal()
for len(b) > 0 {
var (
authorizedKey ssh.PublicKey
comment string
)
if authorizedKey, comment, _, b, err = ssh.ParseAuthorizedKey(b); err != nil {
break
}
log.WithFields(logger.Fields{
"authorized_key": ssh.FingerprintSHA256(authorizedKey),
"authorized_key_type": authorizedKey.Type(),
"authorized_key_comment": comment,
}).Debug("checking key")
if bytes.Equal(authorizedKey.Marshal(), marshaled) {
return &ssh.Permissions{
Extensions: map[string]string{
"gate-idp": "system",
"gate-obj": user.Login(),
},
}, nil
}
}
return nil, errors.New("key not found")
}
type systemUser struct {
idp IdentityProvider
obj *user.User
}
func (user systemUser) ID() compact.ID {
return compact.String(user.obj.Uid)
}
func (user systemUser) Login() string { return user.obj.Username }
func (user systemUser) Name() string { return user.obj.Name }
func (user systemUser) Groups() []Group {
var (
ids, _ = user.obj.GroupIds()
groups = make([]Group, 0, len(ids))
)
for _, id := range ids {
if group, err := user.idp.LookupGroup(id); err == nil {
groups = append(groups, group)
}
}
return groups
}
type systemGroup struct {
idp IdentityProvider
obj *user.Group
}
func (group systemGroup) ID() compact.ID {
return compact.String("gid=" + group.obj.Gid)
}
func (group systemGroup) Name() string {
return group.obj.Name
}
func (group systemGroup) Members() []User {
return nil
}

+ 34
- 0
pkg/core/identity/config.go View File

@ -0,0 +1,34 @@
package identity
import (
"fmt"
"maze.io/gate/pkg/core"
)
// DefaultProvider is our default identity provider.
var DefaultProvider = &systemIdentityProvider{}
type Config struct {
System *systemIdentityProvider
}
func (config *Config) Provider() (core.IdentityProvider, error) {
if config == nil {
// Fast path.
return DefaultProvider, nil
}
var providers []core.IdentityProvider
if config.System != nil {
providers = append(providers, config.System)
}
switch len(providers) {
case 0:
return DefaultProvider, nil
case 1:
return providers[0], nil
default:
return nil, fmt.Errorf("identity: %d providers configured", len(providers))
}
}

+ 1
- 0
pkg/core/identity/ldap.go View File

@ -0,0 +1 @@
package identity

+ 151
- 0
pkg/core/identity/system.go View File

@ -0,0 +1,151 @@
package identity
import (
"bytes"
"errors"
"io/ioutil"
"os/user"
"path/filepath"
"strconv"
"golang.org/x/crypto/ssh"
"maze.io/gate/pkg/core"
"maze.io/gate/pkg/core/logger"
"maze.io/gate/pkg/util/compact"
)
type systemIdentityProvider struct {
log *logger.Logger
}
func (idp *systemIdentityProvider) Setup(log *logger.Logger) error {
idp.log = log
return nil
}
func (idp *systemIdentityProvider) LookupUser(name string) (core.User, error) {
u, err := user.Lookup(name)
if err != nil {
return nil, err
}
return systemUser{idp, u}, nil
}
func (idp *systemIdentityProvider) LookupGroup(name string) (core.Group, error) {
if _, err := strconv.Atoi(name); err == nil {
g, err := user.LookupGroupId(name)
if err != nil {
return nil, err
}
return systemGroup{idp, g}, nil
}
g, err := user.LookupGroup(name)
if err != nil {
return nil, err
}
return systemGroup{idp, g}, nil
}
func (idp *systemIdentityProvider) PasswordCallback(conn ssh.ConnMetadata, password []byte) (*ssh.Permissions, error) {
user, err := idp.LookupUser(conn.User())
if err != nil {
return nil, err
}
_ = user
return nil, errors.New("password not implemented")
}
type systemKey struct {
ssh.PublicKey
Options []string
}
func (idp systemIdentityProvider) PublicKeyCallback(conn ssh.ConnMetadata, key ssh.PublicKey) (*ssh.Permissions, error) {
log := logger.Default.WithFields(logger.Fields{
logger.User: conn.User(),
"key": ssh.FingerprintSHA256(key),
"key_type": key.Type(),
}).WithSourceAddr(conn.RemoteAddr())
log.Debug("public key authentication")
user, err := idp.LookupUser(conn.User())
if err != nil {
return nil, err
}
name := filepath.Join(user.(systemUser).obj.HomeDir, ".ssh", "authorized_keys")
b, err := ioutil.ReadFile(name)
if err != nil {
return nil, err
}
marshaled := key.Marshal()
for len(b) > 0 {
var (
authorizedKey ssh.PublicKey
comment string
)
if authorizedKey, comment, _, b, err = ssh.ParseAuthorizedKey(b); err != nil {
break
}
log.WithFields(logger.Fields{
"authorized_key": ssh.FingerprintSHA256(authorizedKey),
"authorized_key_type": authorizedKey.Type(),
"authorized_key_comment": comment,
}).Debug("checking key")
if bytes.Equal(authorizedKey.Marshal(), marshaled) {
return &ssh.Permissions{
Extensions: map[string]string{
"gate-idp": "system",
"gate-obj": user.Login(),
},
}, nil
}
}
return nil, errors.New("key not found")
}
type systemUser struct {
idp core.IdentityProvider
obj *user.User
}
func (user systemUser) ID() compact.ID {
return compact.String(user.obj.Uid)
}
func (user systemUser) Login() string { return user.obj.Username }
func (user systemUser) Name() string { return user.obj.Name }
func (user systemUser) Groups() []core.Group {
var (
ids, _ = user.obj.GroupIds()
groups = make([]core.Group, 0, len(ids))
)
for _, id := range ids {
if group, err := user.idp.LookupGroup(id); err == nil {
groups = append(groups, group)
}
}
return groups
}
type systemGroup struct {
idp core.IdentityProvider
obj *user.Group
}
func (group systemGroup) ID() compact.ID {
return compact.String("gid=" + group.obj.Gid)
}
func (group systemGroup) Name() string {
return group.obj.Name
}
func (group systemGroup) Members() []core.User {
return nil
}

+ 3
- 0
pkg/core/service.go View File

@ -12,6 +12,9 @@ type Service interface {
// Component returns the name of the service component.
Component() string
// Setup the service.
Setup(provider IdentityProvider) error
// Start the service.
Start(*logger.Logger, chan<- error) error


+ 5
- 0
pkg/net/secureshell/server.go View File

@ -53,6 +53,11 @@ func (sshd *Server) Component() string {
return "sshd"
}
func (sshd *Server) Setup(provider core.IdentityProvider) error {
sshd.IdentityProvider = provider
return nil
}
func (sshd *Server) Start(log *logger.Logger, errs chan<- error) error {
sshd.log = log.WithField(logger.Component, sshd.Component())
sshd.conns = make(map[compact.ID]*transport)


+ 8
- 0
pkg/net/web/server.go View File

@ -31,6 +31,9 @@ type Server struct {
// Static files directory.
Static string
// IdentityProvider
IdentityProvider core.IdentityProvider
log *logger.Logger
listener net.Listener
services []core.Service
@ -53,6 +56,11 @@ func (httpd *Server) setup(log *logger.Logger) {
}
}
func (httpd *Server) Setup(provider core.IdentityProvider) error {
httpd.IdentityProvider = provider
return nil
}
func (httpd *Server) Start(log *logger.Logger, errors chan<- error) error {
httpd.setup(log)


Loading…
Cancel
Save