pdns-auth-proxy/auth.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 ""
}