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 "" }