improve permissions system

This commit is contained in:
Xavier Henner 2019-07-17 19:12:00 +02:00
parent 0d918b7540
commit e18aa583d0
5 changed files with 90 additions and 36 deletions

View File

@ -26,13 +26,13 @@ type jsonInputParams struct {
} }
type HttpServer struct { type HttpServer struct {
Port string Port string
ovpn *OpenVpnMgt ovpn *OpenVpnMgt
key string key string
cert string cert string
minProfile string minProfile string
neededProfile string neededProfiles []string
certPool *x509.CertPool certPool *x509.CertPool
} }
func parseJsonQuery(r *http.Request) (*jsonInput, error) { 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) webuser := strings.Replace(r.TLS.PeerCertificates[0].Subject.CommonName, " ", "", -1)
profile, _, _ := h.ovpn.AuthLoop(h.minProfile, webuser, "", false) _, _, _, profilePath := h.ovpn.AuthLoop(h.minProfile, webuser, "", false)
if profile != h.neededProfile { if inArray(h.neededProfiles, profilePath) {
http.Error(w, fmt.Sprintf("You need the %s profile", h.neededProfile), 403) http.Error(w, fmt.Sprintf("You need on of %s profile", h.neededProfiles), 403)
return return
} }
log.Printf("%s is connected via the web interfaces\n", webuser) 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 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{ h := &HttpServer{
Port: port, Port: port,
ovpn: s, ovpn: s,
key: key, key: key,
cert: cert, cert: cert,
minProfile: minProfile, minProfile: minProfile,
neededProfile: neededProfile, neededProfiles: neededProfiles,
} }
http.HandleFunc("/help", h.helpHandler) http.HandleFunc("/help", h.helpHandler)

20
ldap.go
View File

@ -25,7 +25,7 @@ type ldapConfig struct {
certAuth string certAuth string
ipMin net.IP ipMin net.IP
ipMax net.IP ipMax net.IP
upgradeFrom string upgradeFrom []string
routes []string routes []string
} }
@ -46,21 +46,31 @@ func (l *ldapConfig) addIPRange(s string) error {
// auth loop. Try all auth profiles from startProfile // auth loop. Try all auth profiles from startProfile
// return the last possible profile and the mail if we found a mail like login // 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} login := []string{user}
profile := startProfile profile := startProfile
mail := "" mail := ""
otpSalt := "" otpSalt := ""
profilePath := []string{}
re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$") re := rcache.Get("^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+.[a-zA-Z0-9-.]+$")
for { for {
// the first login that match the mail regexp is the mail address
if mail == "" && re.MatchString(login[0]) { if mail == "" && re.MatchString(login[0]) {
mail = login[0] mail = login[0]
} }
n := profile n := profile
for k, ldap := range s.ldap { 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 continue
} }
err, userOk, passOk, attributes := ldap.Auth(login, pass) 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 // we have either a positive auth ok a previous valid one
if passOk || profile != "" || overridePwdCheck { if passOk || profile != "" || overridePwdCheck {
profile = k 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 // override the real DialTLS function

View File

@ -74,7 +74,7 @@ func main() {
routes: parseConfigArray(config, profile+".routes"), routes: parseConfigArray(config, profile+".routes"),
mfaType: config.GetString(profile+".mfa", ""), mfaType: config.GetString(profile+".mfa", ""),
certAuth: config.GetString(profile+".cert", "optionnal"), 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 { if err := ldapConf.addIPRange(config.GetString(profile+".IPRange", "")); err != nil {
log.Println(err) log.Println(err)
@ -97,6 +97,6 @@ func main() {
config.GetString("config.http.cert", ""), config.GetString("config.http.cert", ""),
config.GetString("config.http.ca", ""), config.GetString("config.http.ca", ""),
config.GetString("config.http.startAuth", "CORP"), config.GetString("config.http.startAuth", "CORP"),
config.GetString("config.http.reqAuth", "ADMINS"), parseConfigArray(config, "config.http.reqAuth"),
server) server)
} }

View File

@ -2,6 +2,26 @@ config
{ {
profiles: 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: CONTRACT:
{ {
servers: [ "dc-11.office.daily","dc-12.office.daily","dc-13.office.daily" ] 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", bindCn: "CN=VPN Service,OU=Services,OU=Dailymotion,DC=office,DC=daily",
bindPw: "********************", bindPw: "********************",
searchFilter: "(&(sAMAccountName=%s))" searchFilter: "(&(sAMAccountName=%s))"
attributes: [ "memberOf", "mail" ] attributes: [ "memberOf", "mail", "extensionAttribute8" ]
validGroups: validGroups:
[ [
"CN=SEC_VPN_Users_External,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", "CN=SEC_VPN_Users_External,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily",
] ]
mfa: "okta" mfa: "internal"
cert: "ignore"
IPRange: "192.168.207.1 - 192.168.207.254", IPRange: "192.168.207.1 - 192.168.207.254",
routes: routes:
[ [
@ -38,15 +57,32 @@ config
bindCn: "CN=VPN Service,OU=Services,OU=Dailymotion,DC=office,DC=daily", bindCn: "CN=VPN Service,OU=Services,OU=Dailymotion,DC=office,DC=daily",
bindPw: "********************", bindPw: "********************",
searchFilter: "(&(sAMAccountName=%s))" searchFilter: "(&(sAMAccountName=%s))"
attributes: [ "memberOf", "mail" ] attributes: [ "memberOf", "mail", "extensionAttribute8" ]
validGroups: validGroups:
[ [
"CN=SEC_VPN,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily", "CN=SEC_VPN,OU=Security,OU=Groups,OU=Dailymotion,DC=office,DC=daily",
] ]
mfa: "okta" mfa: ""
cert: "optionnal"
IPRange: "192.168.201.1-192.168.203.254" 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: DEV:
{ {
servers: [ "ldap-auth.vip.dailymotion.com" ] servers: [ "ldap-auth.vip.dailymotion.com" ]
@ -55,9 +91,8 @@ config
bindPw: "**********" bindPw: "**********"
searchFilter: "(&(mail=%s))" searchFilter: "(&(mail=%s))"
attributes: [ "description", "sshPublicKey" ] attributes: [ "description", "sshPublicKey" ]
upgradeFrom: "CORP" upgradeFrom: [ "CORP", "IT-AND-SEC" ]
mfa: "" mfa: ""
cert: "optionnal"
IPRange: "192.168.204.1-192.168.206.254" IPRange: "192.168.204.1-192.168.206.254"
} }
ADMINS: ADMINS:
@ -66,11 +101,19 @@ config
[ [
"infra", "infra",
"net", "net",
]
upgradeFrom: [ "DEV" ]
mfa: "internal"
IPRange: "192.168.200.2-192.168.200.254"
}
DATACENTER:
{
validGroups:
[
"datacenter", "datacenter",
] ]
upgradeFrom: "DEV" upgradeFrom: [ "DEV" ]
mfa: "internal" mfa: "internal"
cert: "mandatory"
IPRange: "192.168.200.2-192.168.200.254" IPRange: "192.168.200.2-192.168.200.254"
} }
} }
@ -82,9 +125,8 @@ config
key: "/etc/ssl/private/server-key.pem" key: "/etc/ssl/private/server-key.pem"
cert: "/etc/ssl/certs/server-bundle.pem" cert: "/etc/ssl/certs/server-bundle.pem"
startAuth: "CORP" startAuth: "CORP"
reqAuth: "ADMINS" reqAuth: [ "ADMINS", "IP-AND-SEC" ]
} }
cacheDir: "/var/run/openvpn/" cacheDir: "/var/run/openvpn/"
authCa: "/usr/local/share/ca-certificates/Dailymotion.crt" authCa: "/usr/local/share/ca-certificates/Dailymotion.crt"
masterSecrets: [ "********************************" ] masterSecrets: [ "********************************" ]

View File

@ -266,7 +266,7 @@ func (c *vpnSession) auth(s *OpenVpnMgt) (error, int) {
} }
otpSalt := "" 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 // no profile validated, we stop here
if c.Profile == "" { if c.Profile == "" {