From e18aa583d0ecb69fcad6d2d4d60846b9a376d7ab Mon Sep 17 00:00:00 2001 From: Xavier Henner Date: Wed, 17 Jul 2019 19:12:00 +0200 Subject: [PATCH] improve permissions system --- httpd.go | 34 +++++++-------- ldap.go | 20 +++++++-- main.go | 4 +- openvpn-dm-mgt-server.conf.example | 66 ++++++++++++++++++++++++------ vpnsession.go | 2 +- 5 files changed, 90 insertions(+), 36 deletions(-) diff --git a/httpd.go b/httpd.go index ad5838b..cdde00d 100644 --- a/httpd.go +++ b/httpd.go @@ -26,13 +26,13 @@ type jsonInputParams struct { } type HttpServer struct { - Port string - ovpn *OpenVpnMgt - key string - cert string - minProfile string - neededProfile string - certPool *x509.CertPool + Port string + ovpn *OpenVpnMgt + key string + cert string + minProfile string + neededProfiles []string + certPool *x509.CertPool } func parseJsonQuery(r *http.Request) (*jsonInput, error) { @@ -119,9 +119,9 @@ func (h *HttpServer) ajaxHandler(w http.ResponseWriter, r *http.Request) { } webuser := strings.Replace(r.TLS.PeerCertificates[0].Subject.CommonName, " ", "", -1) - profile, _, _ := h.ovpn.AuthLoop(h.minProfile, webuser, "", false) - if profile != h.neededProfile { - http.Error(w, fmt.Sprintf("You need the %s profile", h.neededProfile), 403) + _, _, _, profilePath := h.ovpn.AuthLoop(h.minProfile, webuser, "", false) + if inArray(h.neededProfiles, profilePath) { + http.Error(w, fmt.Sprintf("You need on of %s profile", h.neededProfiles), 403) return } log.Printf("%s is connected via the web interfaces\n", webuser) @@ -152,14 +152,14 @@ func (h *HttpServer) ajaxHandler(w http.ResponseWriter, r *http.Request) { return } -func NewHTTPServer(port, key, cert, ca, minProfile, neededProfile string, s *OpenVpnMgt) { +func NewHTTPServer(port, key, cert, ca, minProfile string, neededProfiles []string, s *OpenVpnMgt) { h := &HttpServer{ - Port: port, - ovpn: s, - key: key, - cert: cert, - minProfile: minProfile, - neededProfile: neededProfile, + Port: port, + ovpn: s, + key: key, + cert: cert, + minProfile: minProfile, + neededProfiles: neededProfiles, } http.HandleFunc("/help", h.helpHandler) diff --git a/ldap.go b/ldap.go index 5ec7cf9..dee130b 100644 --- a/ldap.go +++ b/ldap.go @@ -25,7 +25,7 @@ type ldapConfig struct { certAuth string ipMin net.IP ipMax net.IP - upgradeFrom string + upgradeFrom []string routes []string } @@ -46,21 +46,31 @@ func (l *ldapConfig) addIPRange(s string) error { // auth loop. Try all auth profiles from startProfile // return the last possible profile and the mail if we found a mail like login -func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck bool) (string, string, string) { +func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck bool) (string, string, string, []string) { login := []string{user} profile := startProfile mail := "" otpSalt := "" + profilePath := []string{} re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$") for { + // the first login that match the mail regexp is the mail address if mail == "" && re.MatchString(login[0]) { mail = login[0] } n := profile for k, ldap := range s.ldap { - if ldap.upgradeFrom != profile { + // check if the current profile is an upgrade of the previous one + // check the startup profile first + ok := (profile == "") && (len(ldap.upgradeFrom) == 0) + for _, possible := range ldap.upgradeFrom { + if possible == profile { + ok = true + } + } + if !ok { continue } err, userOk, passOk, attributes := ldap.Auth(login, pass) @@ -90,6 +100,8 @@ func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck // we have either a positive auth ok a previous valid one if passOk || profile != "" || overridePwdCheck { profile = k + profilePath = append(profilePath, profile) + break } } } @@ -100,7 +112,7 @@ func (s *OpenVpnMgt) AuthLoop(startProfile, user, pass string, overridePwdCheck } } - return profile, mail, otpSalt + return profile, mail, otpSalt, profilePath } // override the real DialTLS function diff --git a/main.go b/main.go index 1405587..078e058 100644 --- a/main.go +++ b/main.go @@ -74,7 +74,7 @@ func main() { routes: parseConfigArray(config, profile+".routes"), mfaType: config.GetString(profile+".mfa", ""), certAuth: config.GetString(profile+".cert", "optionnal"), - upgradeFrom: config.GetString(profile+".upgradeFrom", ""), + upgradeFrom: parseConfigArray(config, profile+".upgradeFrom"), } if err := ldapConf.addIPRange(config.GetString(profile+".IPRange", "")); err != nil { log.Println(err) @@ -97,6 +97,6 @@ func main() { config.GetString("config.http.cert", ""), config.GetString("config.http.ca", ""), config.GetString("config.http.startAuth", "CORP"), - config.GetString("config.http.reqAuth", "ADMINS"), + parseConfigArray(config, "config.http.reqAuth"), server) } diff --git a/openvpn-dm-mgt-server.conf.example b/openvpn-dm-mgt-server.conf.example index 8b6577b..386cb92 100644 --- a/openvpn-dm-mgt-server.conf.example +++ b/openvpn-dm-mgt-server.conf.example @@ -2,6 +2,26 @@ config { profiles: { +################################################################### +### Security Model ### +### ### +### +---> CONTRACT ### +### | ### +### start-here +-------> DATACENTER ### +### | | ### +### +---> CORP ------------> DEV -----> ADMINS ### +### | ^ ### +### | | ### +### +--> IT-AND-SEC ---+ ### +### ### +### CORP/IT-AND-SEC have the same IPs but not the web perms ### +### ADMIN/DATACENTER have the same IPs but not the web perms ### +### ### +### attributes[0] must match validGroups ### +### attributes[1] is the login for the next security groups ### +### attributes[2] is used as a salt for mfa generation ### +### ### +################################################################### CONTRACT: { servers: [ "dc-11.office.daily","dc-12.office.daily","dc-13.office.daily" ] @@ -9,13 +29,12 @@ config bindCn: "CN=VPN Service,OU=Services,OU=Dailymotion,DC=office,DC=daily", bindPw: "********************", searchFilter: "(&(sAMAccountName=%s))" - attributes: [ "memberOf", "mail" ] + attributes: [ "memberOf", "mail", "extensionAttribute8" ] validGroups: [ "CN=SEC_VPN_Users_External,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", ] - mfa: "okta" - cert: "ignore" + mfa: "internal" IPRange: "192.168.207.1 - 192.168.207.254", routes: [ @@ -38,15 +57,32 @@ config bindCn: "CN=VPN Service,OU=Services,OU=Dailymotion,DC=office,DC=daily", bindPw: "********************", searchFilter: "(&(sAMAccountName=%s))" - attributes: [ "memberOf", "mail" ] + attributes: [ "memberOf", "mail", "extensionAttribute8" ] validGroups: [ "CN=SEC_VPN,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", ] - mfa: "okta" - cert: "optionnal" + mfa: "" IPRange: "192.168.201.1-192.168.203.254" } + IT-AND-SEC: + { + servers: [ "dc-11.office.daily","dc-12.office.daily","dc-13.office.daily" ] + baseDN: "OU=Dailymotion,DC=office,DC=daily", + bindCn: "CN=VPN Service,OU=Services,OU=Dailymotion,DC=office,DC=daily", + bindPw: "********************", + searchFilter: "(&(mail=%s))" + attributes: [ "memberOf", "mail", "extensionAttribute8" ] + upgradeFrom: [ "CORP" ] + validGroups: + [ + "CN=IT-Office,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", + "CN=Security,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", + ] + mfa: "" + IPRange: "192.168.201.1-192.168.203.254" + } + DEV: { servers: [ "ldap-auth.vip.dailymotion.com" ] @@ -55,9 +91,8 @@ config bindPw: "**********" searchFilter: "(&(mail=%s))" attributes: [ "description", "sshPublicKey" ] - upgradeFrom: "CORP" + upgradeFrom: [ "CORP", "IT-AND-SEC" ] mfa: "" - cert: "optionnal" IPRange: "192.168.204.1-192.168.206.254" } ADMINS: @@ -66,11 +101,19 @@ config [ "infra", "net", + ] + upgradeFrom: [ "DEV" ] + mfa: "internal" + IPRange: "192.168.200.2-192.168.200.254" + } + DATACENTER: + { + validGroups: + [ "datacenter", ] - upgradeFrom: "DEV" + upgradeFrom: [ "DEV" ] mfa: "internal" - cert: "mandatory" IPRange: "192.168.200.2-192.168.200.254" } } @@ -82,9 +125,8 @@ config key: "/etc/ssl/private/server-key.pem" cert: "/etc/ssl/certs/server-bundle.pem" startAuth: "CORP" - reqAuth: "ADMINS" + reqAuth: [ "ADMINS", "IP-AND-SEC" ] } - cacheDir: "/var/run/openvpn/" authCa: "/usr/local/share/ca-certificates/Dailymotion.crt" masterSecrets: [ "********************************" ] diff --git a/vpnsession.go b/vpnsession.go index e59250b..6d5a242 100644 --- a/vpnsession.go +++ b/vpnsession.go @@ -266,7 +266,7 @@ func (c *vpnSession) auth(s *OpenVpnMgt) (error, int) { } otpSalt := "" - c.Profile, c.Mail, otpSalt = s.AuthLoop("", c.Login, c.password, tokenPasswordOk) + c.Profile, c.Mail, otpSalt, _ = s.AuthLoop("", c.Login, c.password, tokenPasswordOk) // no profile validated, we stop here if c.Profile == "" {