Commit 237b91de authored by Marko Mikulicic's avatar Marko Mikulicic Committed by GitHub

Merge pull request #106 from cesanta/extauth

External authentication
parents 06ded30f 47020de6
......@@ -15,6 +15,7 @@ Supported authentication methods:
* Google Sign-In (incl. Google for Work / GApps for domain) (documented [here](https://github.com/cesanta/docker_auth/blob/master/examples/reference.yml))
* LDAP bind
* MongoDB user collection
* External program
Supported authorization methods:
* Static ACL
......
/*
Copyright 2016 Cesanta Software Ltd.
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
https://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 authn
import (
"encoding/json"
"fmt"
"os/exec"
"strings"
"syscall"
"github.com/golang/glog"
)
type ExtAuthConfig struct {
Command string `yaml:"command"`
Args []string `yaml:"args"`
}
type ExtAuthRequest struct {
User string `json:"user"`
Password string `json:"password"`
}
type ExtAuthStatus int
const (
ExtAuthAllowed ExtAuthStatus = 0
ExtAuthDenied ExtAuthStatus = 1
ExtAuthNoMatch ExtAuthStatus = 2
ExtAuthError ExtAuthStatus = 3
)
type ExtAuthResponse struct {
Status int `json:"status"`
Message string `json:"message,omitempty"`
}
func (c *ExtAuthConfig) Validate() error {
if c.Command == "" {
return fmt.Errorf("command is not set")
}
if _, err := exec.LookPath(c.Command); err != nil {
return fmt.Errorf("invalid command %q: %s", c.Command, err)
}
return nil
}
type extAuth struct {
cfg *ExtAuthConfig
}
func (r ExtAuthRequest) String() string {
if r.Password != "" {
r.Password = "***"
}
b, _ := json.Marshal(r)
return string(b)
}
func NewExtAuth(cfg *ExtAuthConfig) *extAuth {
glog.Infof("External authenticator: %s %s", cfg.Command, strings.Join(cfg.Args, " "))
return &extAuth{cfg: cfg}
}
func (ea *extAuth) Authenticate(user string, password PasswordString) (bool, error) {
cmd := exec.Command(ea.cfg.Command, ea.cfg.Args...)
cmd.Stdin = strings.NewReader(fmt.Sprintf("%s %s", user, string(password)))
_, err := cmd.Output()
es := 0
et := ""
if err == nil {
} else if ee, ok := err.(*exec.ExitError); ok {
es = ee.Sys().(syscall.WaitStatus).ExitStatus()
et = string(ee.Stderr)
} else {
es = int(ExtAuthError)
et = fmt.Sprintf("cmd run error: %s", err)
}
glog.V(2).Infof("%s %s -> %d", cmd.Path, cmd.Args, es)
switch ExtAuthStatus(es) {
case ExtAuthAllowed:
return true, nil
case ExtAuthDenied:
return false, nil
case ExtAuthNoMatch:
return false, NoMatch
default:
glog.Errorf("Ext command error: %d %s", es, et)
}
return false, fmt.Errorf("bad return code from command: %d", es)
}
func (sua *extAuth) Stop() {
}
func (sua *extAuth) Name() string {
return "external"
}
File mode changed from 100755 to 100644
......@@ -37,6 +37,7 @@ type Config struct {
GoogleAuth *authn.GoogleAuthConfig `yaml:"google_auth,omitempty"`
LDAPAuth *authn.LDAPAuthConfig `yaml:"ldap_auth,omitempty"`
MongoAuth *authn.MongoAuthConfig `yaml:"mongo_auth,omitempty"`
ExtAuth *authn.ExtAuthConfig `yaml:"ext_auth,omitempty"`
ACL authz.ACL `yaml:"acl,omitempty"`
ACLMongo *authz.ACLMongoConfig `yaml:"acl_mongo,omitempty"`
}
......@@ -72,7 +73,7 @@ func validate(c *Config) error {
if c.Token.Expiration <= 0 {
return fmt.Errorf("expiration must be positive, got %d", c.Token.Expiration)
}
if c.Users == nil && c.GoogleAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil {
if c.Users == nil && c.ExtAuth == nil && c.GoogleAuth == nil && c.LDAPAuth == nil && c.MongoAuth == nil {
return errors.New("no auth methods are configured, this is probably a mistake. Use an empty user map if you really want to deny everyone.")
}
if c.MongoAuth != nil {
......@@ -95,6 +96,11 @@ func validate(c *Config) error {
gac.HTTPTimeout = 10
}
}
if c.ExtAuth != nil {
if err := c.ExtAuth.Validate(); err != nil {
return fmt.Errorf("bad ext_auth config: %s", err)
}
}
if c.ACL == nil && c.ACLMongo == nil {
return errors.New("ACL is empty, this is probably a mistake. Use an empty list if you really want to deny all actions")
}
......
......@@ -62,6 +62,9 @@ func NewAuthServer(c *Config) (*AuthServer, error) {
if c.Users != nil {
as.authenticators = append(as.authenticators, authn.NewStaticUserAuth(c.Users))
}
if c.ExtAuth != nil {
as.authenticators = append(as.authenticators, authn.NewExtAuth(c.ExtAuth))
}
if c.GoogleAuth != nil {
ga, err := authn.NewGoogleAuth(c.GoogleAuth)
if err != nil {
......
......@@ -103,6 +103,13 @@ mongo_auth:
# Unlike acl_mongo we don't cache the full user set. We just query mongo for
# an exact match for each authorization
# External authentication - call an external progam to authenticate user.
# Username and password are passed to command's stdin and exit code is examined.
# 0 - allow, 1 - deny, 2 - no match, other - error.
ext_auth:
command: "/usr/local/bin/my_auth" # Can be a relative path too; $PATH works.
args: ["--flag", "--more", "--flags"]
# Authorization methods. All are tried, any one returning success is sufficient.
# At least one must be configured.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment