Files
ziti/controller/model/authenticator_handlers.go
T

607 lines
17 KiB
Go

/*
Copyright NetFoundry, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package model
import (
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"github.com/michaelquigley/pfxlog"
"github.com/openziti/edge/controller/apierror"
"github.com/openziti/edge/controller/persistence"
"github.com/openziti/edge/crypto"
edgeCert "github.com/openziti/edge/internal/cert"
"github.com/openziti/fabric/controller/models"
"github.com/openziti/storage/ast"
"github.com/openziti/storage/boltz"
"github.com/openziti/foundation/util/errorz"
nfpem "github.com/openziti/foundation/util/pem"
"go.etcd.io/bbolt"
"reflect"
"strings"
"time"
)
type AuthenticatorHandler struct {
baseHandler
authStore persistence.AuthenticatorStore
}
func (handler AuthenticatorHandler) Delete(id string) error {
return handler.deleteEntity(id)
}
func (handler AuthenticatorHandler) IsUpdated(field string) bool {
return !strings.EqualFold(field, "method") && !strings.EqualFold(field, "identityId")
}
func NewAuthenticatorHandler(env Env) *AuthenticatorHandler {
handler := &AuthenticatorHandler{
baseHandler: newBaseHandler(env, env.GetStores().Authenticator),
authStore: env.GetStores().Authenticator,
}
handler.impl = handler
return handler
}
func (handler AuthenticatorHandler) newModelEntity() boltEntitySink {
return &Authenticator{}
}
func (handler AuthenticatorHandler) IsAuthorized(authContext AuthContext) (*Identity, string, error) {
authModule := handler.env.GetAuthRegistry().GetByMethod(authContext.GetMethod())
if authModule == nil {
return nil, "", apierror.NewInvalidAuthMethod()
}
identityId, externalId, authenticatorId, err := authModule.Process(authContext)
if err != nil {
return nil, "", err
}
if externalId != "" {
identity, err := handler.env.GetHandlers().Identity.ReadByExternalId(externalId)
return identity, authenticatorId, err
}
if identityId != "" {
identity, err := handler.env.GetHandlers().Identity.Read(identityId)
return identity, authenticatorId, err
}
return nil, "", apierror.NewInvalidAuth()
}
func (handler AuthenticatorHandler) ReadFingerprints(authenticatorId string) ([]string, error) {
var authenticator *persistence.Authenticator
err := handler.env.GetStores().DbProvider.GetDb().View(func(tx *bbolt.Tx) error {
var err error
authenticator, err = handler.authStore.LoadOneById(tx, authenticatorId)
return err
})
if err != nil {
return nil, err
}
return authenticator.ToSubType().Fingerprints(), nil
}
func (handler *AuthenticatorHandler) Read(id string) (*Authenticator, error) {
modelEntity := &Authenticator{}
if err := handler.readEntity(id, modelEntity); err != nil {
return nil, err
}
return modelEntity, nil
}
func (handler *AuthenticatorHandler) Create(authenticator *Authenticator) (string, error) {
if authenticator.Method != persistence.MethodAuthenticatorUpdb && authenticator.Method != persistence.MethodAuthenticatorCert {
return "", errorz.NewFieldError("method must be updb or cert", "method", authenticator.Method)
}
queryString := fmt.Sprintf(`method = "%s"`, authenticator.Method)
query, err := ast.Parse(handler.GetStore(), queryString)
if err != nil {
return "", err
}
result, err := handler.ListForIdentity(authenticator.IdentityId, query)
if err != nil {
return "", err
}
if result.GetMetaData().Count > 0 {
return "", apierror.NewAuthenticatorMethodMax()
}
if authenticator.Method == persistence.MethodAuthenticatorUpdb {
if updb, ok := authenticator.SubType.(*AuthenticatorUpdb); ok {
hashResult := handler.HashPassword(updb.Password)
updb.Password = hashResult.Password
updb.Salt = hashResult.Salt
}
}
if authenticator.Method == persistence.MethodAuthenticatorCert {
certs := nfpem.PemStringToCertificates(authenticator.ToCert().Pem)
if len(certs) != 1 {
err := apierror.NewCouldNotParsePem()
err.Cause = errors.New("client pem must be exactly one certificate")
err.AppendCause = true
return "", err
}
cert := certs[0]
fingerprint := handler.env.GetFingerprintGenerator().FromCert(cert)
authenticator.ToCert().Fingerprint = fingerprint
opts := x509.VerifyOptions{
Roots: handler.getRootPool(),
Intermediates: x509.NewCertPool(),
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageAny},
CurrentTime: cert.NotBefore,
}
if _, err := cert.Verify(opts); err != nil {
return "", fmt.Errorf("error verifying client certificate [%s] did not verify against known CAs", fingerprint)
}
}
return handler.createEntity(authenticator)
}
func (handler AuthenticatorHandler) getRootPool() *x509.CertPool {
roots := x509.NewCertPool()
roots.AppendCertsFromPEM(handler.env.GetConfig().CaPems())
err := handler.env.GetHandlers().Ca.Stream("isVerified = true", func(ca *Ca, err error) error {
if ca == nil && err == nil {
return nil
}
if err != nil {
//continue on err
pfxlog.Logger().Errorf("error streaming cas for authentication: %vs", err)
return nil
}
roots.AppendCertsFromPEM([]byte(ca.CertPem))
return nil
})
if err != nil {
return nil
}
return roots
}
func (handler AuthenticatorHandler) ReadByUsername(username string) (*Authenticator, error) {
query := fmt.Sprintf("%s = \"%v\"", persistence.FieldAuthenticatorUpdbUsername, username)
entity, err := handler.readEntityByQuery(query)
if err != nil {
return nil, err
}
if entity == nil {
return nil, nil
}
authenticator, ok := entity.(*Authenticator)
if !ok {
return nil, fmt.Errorf("could not cast from %v to authenticator", reflect.TypeOf(entity))
}
return authenticator, nil
}
func (handler AuthenticatorHandler) ReadByFingerprint(fingerprint string) (*Authenticator, error) {
query := fmt.Sprintf("%s = \"%v\"", persistence.FieldAuthenticatorCertFingerprint, fingerprint)
entity, err := handler.readEntityByQuery(query)
if err != nil {
return nil, err
}
if entity == nil {
return nil, nil
}
authenticator, ok := entity.(*Authenticator)
if !ok {
return nil, fmt.Errorf("could not cast from %v to authenticator", reflect.TypeOf(entity))
}
return authenticator, nil
}
func (handler AuthenticatorHandler) Update(authenticator *Authenticator) error {
if updb := authenticator.ToUpdb(); updb != nil {
hashResult := handler.HashPassword(updb.Password)
updb.Password = hashResult.Password
updb.Salt = hashResult.Salt
}
if cert := authenticator.ToCert(); cert != nil && cert.Pem != "" {
cert.Fingerprint = edgeCert.NewFingerprintGenerator().FromPem([]byte(cert.Pem))
}
return handler.updateEntity(authenticator, handler)
}
func (handler AuthenticatorHandler) UpdateSelf(authenticatorSelf *AuthenticatorSelf) error {
authenticator, err := handler.ReadForIdentity(authenticatorSelf.IdentityId, authenticatorSelf.Id)
if err != nil {
return err
}
if authenticator == nil {
return boltz.NewNotFoundError(handler.authStore.GetSingularEntityType(), "id", authenticatorSelf.Id)
}
if authenticator.IdentityId != authenticatorSelf.IdentityId {
return errorz.NewUnhandled(errors.New("authenticator does not match identity id for update"))
}
updbAuth := authenticator.ToUpdb()
if updbAuth == nil {
return apierror.NewAuthenticatorCannotBeUpdated()
}
curHashResult := handler.ReHashPassword(authenticatorSelf.CurrentPassword, updbAuth.DecodedSalt())
if curHashResult.Password != updbAuth.Password {
apiErr := errorz.NewUnauthorized()
apiErr.Cause = errorz.NewFieldError("invalid current password", "currentPassword", authenticatorSelf.CurrentPassword)
return apiErr
}
updbAuth.Username = authenticatorSelf.Username
updbAuth.Password = authenticatorSelf.NewPassword
updbAuth.Salt = ""
authenticator.SubType = updbAuth
return handler.Update(authenticator)
}
func (handler AuthenticatorHandler) Patch(authenticator *Authenticator, checker boltz.FieldChecker) error {
combinedChecker := &AndFieldChecker{first: handler, second: checker}
return handler.PatchUnrestricted(authenticator, combinedChecker)
}
func (handler AuthenticatorHandler) PatchUnrestricted(authenticator *Authenticator, checker boltz.FieldChecker) error {
if authenticator.Method == persistence.MethodAuthenticatorUpdb {
if updb := authenticator.ToUpdb(); updb != nil {
if checker.IsUpdated("password") {
hashResult := handler.HashPassword(updb.Password)
updb.Password = hashResult.Password
updb.Salt = hashResult.Salt
}
}
}
if authenticator.Method == persistence.MethodAuthenticatorCert {
if cert := authenticator.ToCert(); cert != nil {
if checker.IsUpdated(persistence.FieldAuthenticatorCertPem) {
if cert.Fingerprint = edgeCert.NewFingerprintGenerator().FromPem([]byte(cert.Pem)); cert.Fingerprint == "" {
return apierror.NewCouldNotParsePem()
}
}
}
}
return handler.patchEntity(authenticator, checker)
}
func (handler AuthenticatorHandler) PatchSelf(authenticatorSelf *AuthenticatorSelf, checker boltz.FieldChecker) error {
if checker.IsUpdated("password") {
checker = NewOrFieldChecker(checker, "salt", "password")
}
authenticator, err := handler.ReadForIdentity(authenticatorSelf.IdentityId, authenticatorSelf.Id)
if err != nil {
return err
}
if authenticator == nil {
return boltz.NewNotFoundError(handler.authStore.GetSingularEntityType(), "id", authenticatorSelf.Id)
}
if authenticator.IdentityId != authenticatorSelf.IdentityId {
return errorz.NewUnhandled(errors.New("authenticator does not match identity id for patch"))
}
updbAuth := authenticator.ToUpdb()
if updbAuth == nil {
return apierror.NewAuthenticatorCannotBeUpdated()
}
curHashResult := handler.ReHashPassword(authenticatorSelf.CurrentPassword, updbAuth.DecodedSalt())
if curHashResult.Password != updbAuth.Password {
apiErr := errorz.NewUnauthorized()
apiErr.Cause = errorz.NewFieldError("invalid current password", "currentPassword", authenticatorSelf.CurrentPassword)
return apiErr
}
updbAuth.Username = authenticatorSelf.Username
updbAuth.Password = authenticatorSelf.NewPassword
updbAuth.Salt = ""
authenticator.SubType = updbAuth
return handler.Patch(authenticator, checker)
}
func (handler AuthenticatorHandler) HashPassword(password string) *HashedPassword {
newResult := crypto.Hash(password)
b64Password := base64.StdEncoding.EncodeToString(newResult.Hash)
b64Salt := base64.StdEncoding.EncodeToString(newResult.Salt)
return &HashedPassword{
RawResult: newResult,
Salt: b64Salt,
Password: b64Password,
}
}
func (handler AuthenticatorHandler) ReHashPassword(password string, salt []byte) *HashedPassword {
newResult := crypto.ReHash(password, salt)
b64Password := base64.StdEncoding.EncodeToString(newResult.Hash)
b64Salt := base64.StdEncoding.EncodeToString(newResult.Salt)
return &HashedPassword{
RawResult: newResult,
Salt: b64Salt,
Password: b64Password,
}
}
func (handler AuthenticatorHandler) ListForIdentity(identityId string, query ast.Query) (*AuthenticatorListQueryResult, error) {
filterString := fmt.Sprintf(`identity = "%s"`, identityId)
filter, err := ast.Parse(handler.Store, filterString)
if err != nil {
return nil, err
}
query.SetPredicate(ast.NewAndExprNode(query.GetPredicate(), filter))
result, err := handler.BasePreparedList(query)
if err != nil {
return nil, err
}
var authenticators []*Authenticator
for _, entity := range result.GetEntities() {
if auth, ok := entity.(*Authenticator); ok {
authenticators = append(authenticators, auth)
}
}
return &AuthenticatorListQueryResult{
EntityListResult: result,
Authenticators: authenticators,
}, nil
}
func (handler AuthenticatorHandler) ReadForIdentity(identityId string, authenticatorId string) (*Authenticator, error) {
authenticator, err := handler.Read(authenticatorId)
if err != nil {
return nil, err
}
if authenticator.IdentityId == identityId {
return authenticator, err
}
return nil, nil
}
func (handler AuthenticatorHandler) ExtendCertForIdentity(identityId string, authenticatorId string, peerCerts []*x509.Certificate, csrPem string) ([]byte, error) {
authenticator, _ := handler.Read(authenticatorId)
if authenticator == nil {
return nil, errorz.NewNotFound()
}
if authenticator.Method != persistence.MethodAuthenticatorCert {
return nil, apierror.NewAuthenticatorCannotBeUpdated()
}
if authenticator.IdentityId != identityId {
return nil, errorz.NewUnauthorized()
}
authenticatorCert := authenticator.ToCert()
if authenticatorCert == nil {
return nil, errorz.NewUnhandled(fmt.Errorf("%T is not a %T", authenticator, authenticatorCert))
}
if authenticatorCert.Pem == "" {
return nil, apierror.NewAuthenticatorCannotBeUpdated()
}
var validClientCert *x509.Certificate = nil
for _, cert := range peerCerts {
fingerprint := handler.env.GetFingerprintGenerator().FromCert(cert)
if fingerprint == authenticatorCert.Fingerprint {
validClientCert = cert
break
}
}
if validClientCert == nil {
return nil, errorz.NewUnauthorized()
}
caPool := x509.NewCertPool()
config := handler.env.GetConfig()
caPool.AddCert(config.Enrollment.SigningCert.Cert().Leaf)
validClientCert.NotBefore = time.Now().Add(-1 * time.Hour)
validClientCert.NotAfter = time.Now().Add(+1 * time.Hour)
validChain, err := validClientCert.Verify(x509.VerifyOptions{
Roots: caPool,
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
})
if len(validChain) == 0 || err != nil {
return nil, apierror.NewAuthenticatorCannotBeUpdated()
}
csr, err := edgeCert.ParseCsrPem([]byte(csrPem))
if err != nil {
apiErr := apierror.NewCouldNotProcessCsr()
apiErr.Cause = err
apiErr.AppendCause = true
return nil, apiErr
}
currentCerts := nfpem.PemStringToCertificates(authenticatorCert.Pem)
if len(currentCerts) != 1 {
return nil, errorz.NewUnhandled(errors.New("could not parse current certificates pem"))
}
currentCert := currentCerts[0]
opts := &edgeCert.SigningOpts{
DNSNames: currentCert.DNSNames,
EmailAddresses: currentCert.EmailAddresses,
IPAddresses: currentCert.IPAddresses,
URIs: currentCert.URIs,
}
newRawCert, err := handler.env.GetApiClientCsrSigner().SignCsr(csr, opts)
if err != nil {
apiErr := apierror.NewCouldNotProcessCsr()
apiErr.Cause = err
apiErr.AppendCause = true
return nil, apiErr
}
newFingerprint := handler.env.GetFingerprintGenerator().FromRaw(newRawCert)
newPemCert, err := edgeCert.RawToPem(newRawCert)
if err != nil {
apiErr := apierror.NewCouldNotProcessCsr()
apiErr.Cause = err
apiErr.AppendCause = true
return nil, apiErr
}
authenticatorCert.UnverifiedPem = string(newPemCert)
authenticatorCert.UnverifiedFingerprint = newFingerprint
err = handler.env.GetHandlers().Authenticator.Patch(authenticatorCert.Authenticator, boltz.MapFieldChecker{
persistence.FieldAuthenticatorUnverifiedCertPem: struct{}{},
persistence.FieldAuthenticatorUnverifiedCertFingerprint: struct{}{},
})
if err != nil {
return nil, err
}
return newPemCert, nil
}
func (handler AuthenticatorHandler) VerifyExtendCertForIdentity(identityId, authenticatorId string, verifyCertPem string) error {
authenticator, _ := handler.Read(authenticatorId)
if authenticator == nil {
return errorz.NewNotFound()
}
if authenticator.Method != persistence.MethodAuthenticatorCert {
return apierror.NewAuthenticatorCannotBeUpdated()
}
if authenticator.IdentityId != identityId {
return errorz.NewUnauthorized()
}
authenticatorCert := authenticator.ToCert()
if authenticatorCert == nil {
return errorz.NewUnhandled(fmt.Errorf("%T is not a %T", authenticator, authenticatorCert))
}
if authenticatorCert.Pem == "" {
return apierror.NewAuthenticatorCannotBeUpdated()
}
if authenticatorCert.UnverifiedPem == "" || authenticatorCert.UnverifiedFingerprint == "" {
return apierror.NewAuthenticatorCannotBeUpdated()
}
verifyPrint := nfpem.FingerprintFromPemString(verifyCertPem)
if verifyPrint != authenticatorCert.UnverifiedFingerprint {
return apierror.NewInvalidClientCertificate()
}
authenticatorCert.Pem = authenticatorCert.UnverifiedPem
authenticatorCert.Fingerprint = authenticatorCert.UnverifiedFingerprint
authenticatorCert.UnverifiedFingerprint = ""
authenticatorCert.UnverifiedPem = ""
err := handler.env.GetHandlers().Authenticator.PatchUnrestricted(authenticatorCert.Authenticator, boltz.MapFieldChecker{
persistence.FieldSessionCertFingerprint: struct{}{},
persistence.FieldAuthenticatorUnverifiedCertPem: struct{}{},
persistence.FieldAuthenticatorUnverifiedCertFingerprint: struct{}{},
persistence.FieldAuthenticatorCertPem: struct{}{},
persistence.FieldAuthenticatorCertFingerprint: struct{}{},
})
return err
}
type HashedPassword struct {
RawResult *crypto.HashResult //raw byte hash results
Salt string //base64 encoded hash
Password string //base64 encoded hash
}
type AuthenticatorListQueryResult struct {
*models.EntityListResult
Authenticators []*Authenticator
}