You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2018/05/17 22:41:20 UTC
[incubator-trafficcontrol] 01/19: implement login flow in go for
local user and ldap
This is an automated email from the ASF dual-hosted git repository.
rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git
commit b7c3c2e8158ca45f862980d4c59525e76e43cf27
Author: Dylan Volz <Dy...@comcast.com>
AuthorDate: Tue Mar 13 13:37:28 2018 -0600
implement login flow in go for local user and ldap
---
traffic_ops/traffic_ops_golang/auth/ldap.go | 64 ++++++++++++
traffic_ops/traffic_ops_golang/auth/login.go | 112 +++++++++++++++++++++
traffic_ops/traffic_ops_golang/config/config.go | 40 +++++++-
traffic_ops/traffic_ops_golang/routes.go | 3 +
.../traffic_ops_golang/traffic_ops_golang.go | 3 +-
5 files changed, 220 insertions(+), 2 deletions(-)
diff --git a/traffic_ops/traffic_ops_golang/auth/ldap.go b/traffic_ops/traffic_ops_golang/auth/ldap.go
new file mode 100644
index 0000000..1903505
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/auth/ldap.go
@@ -0,0 +1,64 @@
+package auth
+
+import (
+ "crypto/tls"
+ "errors"
+ "fmt"
+
+ "github.com/apache/incubator-trafficcontrol/lib/go-log"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+
+ "gopkg.in/ldap.v2"
+)
+
+func LookupUserDN(username string, cfg *config.ConfigLDAP) (string, bool, error) {
+ l, err := ldap.DialTLS("tcp", cfg.Host, &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ log.Errorln("error dialing tls")
+ return "", false, err
+ }
+ defer l.Close()
+ // Bind with admin user
+ err = l.Bind(cfg.AdminDN, cfg.AdminPass)
+ if err != nil {
+ log.Errorln("error binding admin user")
+ return "", false, err
+ }
+
+ // Search for the given username
+ searchRequest := ldap.NewSearchRequest(
+ cfg.SearchBase,
+ ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false,
+ fmt.Sprintf("(&(objectCategory=person)(objectClass=user)(sAMAccountName=%s))", username),
+ []string{"dn"},
+ nil,
+ )
+
+ sr, err := l.Search(searchRequest)
+ if err != nil {
+ log.Errorln("error issuing search:")
+ return "", false, err
+ }
+
+ if len(sr.Entries) != 1 {
+ return "", false, errors.New("User does not exist or too many entries returned")
+ }
+ userDN := sr.Entries[0].DN
+ return userDN, true, nil
+}
+
+func AuthenticateUserDN(userDN string, password string, cfg *config.ConfigLDAP) (bool, error) {
+ l, err := ldap.DialTLS("tcp", cfg.Host, &tls.Config{InsecureSkipVerify: true})
+ if err != nil {
+ log.Errorln("error dialing tls")
+ return false, err
+ }
+ defer l.Close()
+
+ // Bind as the user to verify their password
+ err = l.Bind(userDN, password)
+ if err != nil {
+ return false, err
+ }
+ return true, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/auth/login.go b/traffic_ops/traffic_ops_golang/auth/login.go
new file mode 100644
index 0000000..e55a6d8
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/auth/login.go
@@ -0,0 +1,112 @@
+package auth
+
+import (
+ "crypto/sha1"
+ "encoding/hex"
+ "encoding/json"
+ "fmt"
+ "net/http"
+
+ "github.com/jmoiron/sqlx"
+
+ "time"
+
+ "github.com/apache/incubator-trafficcontrol/lib/go-log"
+ "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tocookie"
+ "github.com/pkg/errors"
+)
+
+type passwordForm struct {
+ Username string `json:"u"`
+ Password string `json:"p"`
+}
+
+func LoginHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ handleErrs := tc.GetHandleErrorsFunc(w, r)
+ defer r.Body.Close()
+ form := passwordForm{}
+ if err := json.NewDecoder(r.Body).Decode(&form); err != nil {
+ handleErrs(http.StatusBadRequest, err)
+ return
+ }
+ authenticated, err := checkLocalUser(form, db)
+ if err != nil {
+ log.Errorf("error checking local user: %s\n", err.Error())
+ }
+ var ldapErr error
+ if !authenticated {
+ if cfg.LDAPEnabled {
+ authenticated, ldapErr = checkLDAPUser(form, cfg.ConfigLDAP)
+ if ldapErr != nil {
+ log.Errorf("error checking ldap user: %s\n", ldapErr.Error())
+ }
+ }
+ }
+ resp := struct {
+ tc.Alerts
+ }{}
+ if authenticated {
+ expiry := time.Now().Add(time.Hour * 6)
+ cookie := tocookie.New(form.Username, expiry, cfg.Secrets[0])
+ httpCookie := http.Cookie{Name: "mojolicious", Value: cookie, Path: "/", Expires: expiry, HttpOnly: true}
+ http.SetCookie(w, &httpCookie)
+ resp = struct {
+ tc.Alerts
+ }{tc.CreateAlerts(tc.SuccessLevel, "Successfully logged in.")}
+
+ } else {
+ resp = struct {
+ tc.Alerts
+ }{tc.CreateAlerts(tc.ErrorLevel, "Invalid username or password.")}
+ }
+ respBts, err := json.Marshal(resp)
+ if err != nil {
+ handleErrs(http.StatusInternalServerError, err)
+ return
+ }
+
+ w.Header().Set(tc.ContentType, tc.ApplicationJson)
+ fmt.Fprintf(w, "%s", respBts)
+ }
+}
+
+func checkLocalUser(form passwordForm, db *sqlx.DB) (bool, error) {
+ var hashedPassword string
+ err := db.Get(&hashedPassword, "SELECT local_passwd FROM tm_user WHERE username=$1", form.Username)
+ if err != nil {
+ return false, err
+ }
+ err = VerifyPassword(form.Password, hashedPassword)
+ if err != nil {
+ if hashedPassword == sha1Hex(form.Password) {
+ return true, nil
+ }
+ return false, err
+ }
+ return true, nil
+}
+
+func sha1Hex(s string) string {
+ // SHA1 hash
+ hash := sha1.New()
+ hash.Write([]byte(s))
+ hashBytes := hash.Sum(nil)
+
+ // Hexadecimal conversion
+ hexSha1 := hex.EncodeToString(hashBytes)
+ return hexSha1
+}
+
+func checkLDAPUser(form passwordForm, cfg *config.ConfigLDAP) (bool, error) {
+ userDN, valid, err := LookupUserDN(form.Username, cfg)
+ if err != nil {
+ return false, err
+ }
+ if valid {
+ return AuthenticateUserDN(userDN, form.Password, cfg)
+ }
+ return false, errors.New("User not found in LDAP")
+}
diff --git a/traffic_ops/traffic_ops_golang/config/config.go b/traffic_ops/traffic_ops_golang/config/config.go
index f6d0f10..4864845 100644
--- a/traffic_ops/traffic_ops_golang/config/config.go
+++ b/traffic_ops/traffic_ops_golang/config/config.go
@@ -42,6 +42,8 @@ type Config struct {
// NOTE: don't care about any other fields for now..
RiakAuthOptions *riak.AuthOptions
RiakEnabled bool
+ ConfigLDAP *ConfigLDAP
+ LDAPEnabled bool
Version string
}
@@ -84,6 +86,13 @@ type ConfigDatabase struct {
SSL bool `json:"ssl"`
}
+type ConfigLDAP struct {
+ AdminPass string `json:"admin_pass"`
+ SearchBase string `json:"search_base"`
+ AdminDN string `json:"admin_dn"`
+ Host string `json:"host"`
+}
+
// ErrorLog - critical messages
func (c Config) ErrorLog() log.LogLocation {
return log.LogLocation(c.LogLocationError)
@@ -108,7 +117,7 @@ func (c Config) EventLog() log.LogLocation {
}
// LoadConfig - reads the config file into the Config struct
-func LoadConfig(cdnConfPath string, dbConfPath string, riakConfPath string, appVersion string) (Config, error) {
+func LoadConfig(cdnConfPath string, dbConfPath string, riakConfPath string, ldapConfPath string, appVersion string) (Config, error) {
// load json from cdn.conf
confBytes, err := ioutil.ReadFile(cdnConfPath)
if err != nil {
@@ -142,6 +151,16 @@ func LoadConfig(cdnConfPath string, dbConfPath string, riakConfPath string, appV
}
}
+ if ldapConfPath != "" {
+ cfg.LDAPEnabled, cfg.ConfigLDAP, err = GetLDAPConfig(ldapConfPath)
+ if err != nil {
+ cfg.LDAPEnabled = false // probably unnecessary
+ return cfg, fmt.Errorf("parsing ldap config '%s': %v", ldapConfPath, err)
+ }
+ } else {
+ cfg.LDAPEnabled = false
+ }
+
return cfg, err
}
@@ -226,3 +245,22 @@ func ParseConfig(cfg Config) (Config, error) {
return cfg, nil
}
+
+func GetLDAPConfig(LDAPConfPath string) (bool, *ConfigLDAP, error) {
+ LDAPConfBytes, err := ioutil.ReadFile(LDAPConfPath)
+ if err != nil {
+
+ return false, nil, fmt.Errorf("reading LDAP conf '%v': %v", LDAPConfPath, err)
+ }
+ LDAPconf, err := getLDAPConf(string(LDAPConfBytes))
+ if err != nil {
+ return false, LDAPconf, fmt.Errorf("parsing LDAP conf '%v': %v", LDAPConfBytes, err)
+ }
+ return true, LDAPconf, nil
+}
+
+func getLDAPConf(s string) (*ConfigLDAP, error) {
+ ldapConf := ConfigLDAP{}
+ err := json.Unmarshal([]byte(s), &ldapConf)
+ return &ldapConf, err
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 3437de1..cba2190 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -119,6 +119,9 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
//HWInfo
{1.1, http.MethodGet, `hwinfo-wip/?(\.json)?$`, hwinfo.HWInfoHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+ //Login
+ {1.2, http.MethodPost, `user/login/?$`, auth.LoginHandler(d.DB, d.Config), 0, NoAuth, nil}, {1.3, http.MethodPost, `user/login/?$`, auth.LoginHandler(d.DB, d.Config), 0, NoAuth, nil},
+
//Parameter: CRUD
{1.1, http.MethodGet, `parameters/?(\.json)?$`, api.ReadHandler(parameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
{1.1, http.MethodGet, `parameters/{id}$`, api.ReadHandler(parameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
diff --git a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
index 9de1055..462a58a 100644
--- a/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
+++ b/traffic_ops/traffic_ops_golang/traffic_ops_golang.go
@@ -48,6 +48,7 @@ func main() {
configFileName := flag.String("cfg", "", "The config file path")
dbConfigFileName := flag.String("dbcfg", "", "The db config file path")
riakConfigFileName := flag.String("riakcfg", "", "The riak config file path")
+ ldapConfigFileName := flag.String("ldapcfg", "", "the ldap config file path")
flag.Parse()
if *showVersion {
@@ -63,7 +64,7 @@ func main() {
var err error
var errorToLog error
- if cfg, err = config.LoadConfig(*configFileName, *dbConfigFileName, *riakConfigFileName, version); err != nil {
+ if cfg, err = config.LoadConfig(*configFileName, *dbConfigFileName, *riakConfigFileName, *ldapConfigFileName, version); err != nil {
if !strings.Contains(err.Error(), "riak conf") {
fmt.Println("Error loading config: " + err.Error())
return
--
To stop receiving notification emails like this one, please contact
rob@apache.org.