Initial euclide.org release
This commit is contained in:
242
auth.go
Normal file
242
auth.go
Normal file
@@ -0,0 +1,242 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/rand"
|
||||
"net/mail"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/pyke369/golang-support/rcache"
|
||||
"golang.org/x/crypto/openpgp"
|
||||
)
|
||||
|
||||
// AuthProfile interface
|
||||
type AuthProfile interface {
|
||||
Match(string) (bool, error)
|
||||
PgpKeys() openpgp.EntityList
|
||||
}
|
||||
|
||||
// AuthProfileRegexp implements auth based on regexp only
|
||||
type AuthProfileRegexp struct {
|
||||
subjectRegexp *regexp.Regexp
|
||||
pgpKeys openpgp.EntityList
|
||||
}
|
||||
|
||||
// AuthProfileLdap implements auth based on regexp then ldap check
|
||||
type AuthProfileLdap struct {
|
||||
subjectRegexp *regexp.Regexp
|
||||
ldap *LdapHandler
|
||||
}
|
||||
|
||||
// JSONRPCACL represents the acls for the json RPC API
|
||||
type JSONRPCACL struct {
|
||||
actions map[string][]*regexp.Regexp
|
||||
pgpProfiles []string
|
||||
sslProfiles []string
|
||||
}
|
||||
|
||||
// NewJSONRPCACL instanciates json RPC API ACLs.
|
||||
func NewJSONRPCACL(regexps map[string][]string, pgpProfiles, sslProfiles []string) *JSONRPCACL {
|
||||
realPerms := map[string][]*regexp.Regexp{}
|
||||
for action, reg := range regexps {
|
||||
realPerms[action] = []*regexp.Regexp{}
|
||||
for _, r := range reg {
|
||||
realPerms[action] = append(realPerms[action], rcache.Get(fmt.Sprintf("^%s$", r)))
|
||||
}
|
||||
}
|
||||
a := JSONRPCACL{
|
||||
actions: realPerms,
|
||||
pgpProfiles: pgpProfiles,
|
||||
sslProfiles: sslProfiles,
|
||||
}
|
||||
return &a
|
||||
}
|
||||
|
||||
// GetListFilters returns the restrictions on the list action (or * if // defined)
|
||||
func (a JSONRPCACL) GetListFilters(pgpProfiles, sslProfiles []string, method string) []*regexp.Regexp {
|
||||
var ret []*regexp.Regexp
|
||||
// ignore acl for which the profile doesn't match
|
||||
if !inArray(sslProfiles, a.sslProfiles) && !inArray(pgpProfiles, a.pgpProfiles) {
|
||||
return nil
|
||||
}
|
||||
for aclAction, targets := range a.actions {
|
||||
if aclAction == "*" || aclAction == method {
|
||||
ret = append(ret, targets...)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
// Match method for a json RPC ACL
|
||||
func (a JSONRPCACL) Match(action, target string, pgpProfiles, sslProfiles []string) bool {
|
||||
// ignore acl for which the profile doesn't match
|
||||
if !inArray(sslProfiles, a.sslProfiles) && !inArray(pgpProfiles, a.pgpProfiles) {
|
||||
return false
|
||||
}
|
||||
// the profile match somehow, let's test the action
|
||||
for aclAction, targets := range a.actions {
|
||||
// action must match
|
||||
if aclAction != action && aclAction != "*" {
|
||||
continue
|
||||
}
|
||||
// and target must match too
|
||||
for _, reg := range targets {
|
||||
if reg.MatchString(target) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// PdnsACL represents the acls for direct access to the PDNS API
|
||||
type PdnsACL struct {
|
||||
regexp *regexp.Regexp
|
||||
read bool // Allow GET method
|
||||
write bool // Allow every other method
|
||||
profiles []string
|
||||
}
|
||||
|
||||
// NewPdnsACL instanciates ACLs.
|
||||
func NewPdnsACL(regexp string, perms []string, profiles []string) *PdnsACL {
|
||||
a := PdnsACL{
|
||||
regexp: rcache.Get(fmt.Sprintf("^%s$", regexp)),
|
||||
profiles: profiles,
|
||||
read: false,
|
||||
write: false,
|
||||
}
|
||||
for _, v := range perms {
|
||||
switch v {
|
||||
case "r":
|
||||
a.read = true
|
||||
case "w":
|
||||
a.write = true
|
||||
}
|
||||
}
|
||||
return &a
|
||||
}
|
||||
|
||||
// Match method for an ACL
|
||||
func (a PdnsACL) Match(path, method string, profiles []string) bool {
|
||||
// ignore acl for which the profile doesn't match
|
||||
if !inArray(profiles, a.profiles) {
|
||||
return false
|
||||
}
|
||||
// check the method is valid
|
||||
if (method == "GET" && !a.read) || (method != "GET" && !a.write) {
|
||||
return false
|
||||
}
|
||||
return a.regexp.MatchString(path)
|
||||
}
|
||||
|
||||
// NewAuthProfileRegexp instanciates a regexp based profile
|
||||
func NewAuthProfileRegexp(subjectRegexp, pgpKeys string) AuthProfileRegexp {
|
||||
p := AuthProfileRegexp{
|
||||
subjectRegexp: rcache.Get(fmt.Sprintf("^%s$", subjectRegexp)),
|
||||
}
|
||||
keyring, err := openpgp.ReadArmoredKeyRing(strings.NewReader(pgpKeys))
|
||||
if err == nil {
|
||||
p.pgpKeys = keyring
|
||||
} else {
|
||||
p.pgpKeys = openpgp.EntityList{}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Match method for Regexp based profile
|
||||
func (p AuthProfileRegexp) Match(subject string) (bool, error) {
|
||||
return p.subjectRegexp.MatchString(subject), nil
|
||||
}
|
||||
|
||||
// PgpKeys Method don't apply for a Regexp based profile
|
||||
func (p AuthProfileRegexp) PgpKeys() openpgp.EntityList {
|
||||
return p.pgpKeys
|
||||
}
|
||||
|
||||
// NewAuthProfileLdap instanciates ldap based profile
|
||||
func NewAuthProfileLdap(subjectRegexp string, servers []string,
|
||||
bindCn, bindPw, baseDN, filter, attr, pgpAttr string, valid []string, ssl bool) (AuthProfileLdap, error) {
|
||||
p := AuthProfileLdap{
|
||||
subjectRegexp: rcache.Get(fmt.Sprintf("^%s$", subjectRegexp)),
|
||||
ldap: NewLdap(servers, bindCn, bindPw, baseDN, filter, attr, pgpAttr, valid, 10, ssl),
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// Match Method for ldap based profile
|
||||
func (p AuthProfileLdap) Match(subject string) (bool, error) {
|
||||
if !p.subjectRegexp.MatchString(subject) {
|
||||
return false, nil
|
||||
}
|
||||
return p.ldap.Auth(subject)
|
||||
}
|
||||
|
||||
// PgpKeys Method for ldap based profile
|
||||
func (p AuthProfileLdap) PgpKeys() openpgp.EntityList {
|
||||
var kr openpgp.EntityList
|
||||
ret, err := p.ldap.PgpKeys()
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return nil
|
||||
}
|
||||
if ret == nil {
|
||||
return kr
|
||||
}
|
||||
for _, key := range ret {
|
||||
k, err := openpgp.ReadArmoredKeyRing(strings.NewReader(key))
|
||||
if err != nil {
|
||||
log.Println("Error reading Armored Key: ", err)
|
||||
continue
|
||||
}
|
||||
kr = append(kr, k...)
|
||||
}
|
||||
return kr
|
||||
}
|
||||
|
||||
// NewSalt generate a nonce
|
||||
func NewSalt(size int) string {
|
||||
var letterRunes = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||
b := make([]rune, size)
|
||||
for i := range b {
|
||||
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// ComputeHmac256 return a HMAC, base64 encoded
|
||||
func ComputeHmac256(message string, secret string) string {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
h.Write([]byte(message))
|
||||
return strings.TrimRight(base64.StdEncoding.EncodeToString(h.Sum(nil)), "=")
|
||||
}
|
||||
|
||||
// PgpMessageVerify checks a message signature
|
||||
func PgpMessageVerify(msg []byte, sig []byte, keyring openpgp.EntityList) string {
|
||||
// no keyring, no need to try to decode
|
||||
if len(keyring) == 0 {
|
||||
return ""
|
||||
}
|
||||
signature := bytes.NewReader(sig)
|
||||
message := bytes.NewReader(msg)
|
||||
|
||||
entity, err := openpgp.CheckDetachedSignature(keyring, message, signature)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return ""
|
||||
}
|
||||
for _, a := range entity.Identities {
|
||||
e, err := mail.ParseAddress(a.Name)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return a.Name
|
||||
}
|
||||
return strings.Split(e.Address, "@")[0]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
Reference in New Issue
Block a user