243 lines
6.1 KiB
Go
243 lines
6.1 KiB
Go
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 ""
|
|
}
|