Skip to content

Commit

Permalink
Use Validate Hook to control the total count of AutoCertManager (#1155)
Browse files Browse the repository at this point in the history
* Transform AutoCertManager to a system controller

* Convert AutoCertManager to busisness controller & use validate hook to control the global instance of AutoCertManager

* Allow to not find AutoCertManager

* Use global acm to improve efficiency

* Use global acm in real time
  • Loading branch information
xxx7xxxx committed Dec 8, 2023
1 parent e8dc3e8 commit c3ab195
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 95 deletions.
46 changes: 44 additions & 2 deletions pkg/api/object.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ const (
ObjectAPIResourcesPrefix = "/object-api-resources"
)

func RegisterValidateHook() {}

func (s *Server) objectAPIEntries() []*Entry {
return []*Entry{
{
Expand All @@ -57,7 +59,7 @@ func (s *Server) objectAPIEntries() []*Entry {
{
Path: ObjectAPIResourcesPrefix,
Method: "GET",
Handler: s.listObjectApiResources,
Handler: s.listObjectAPIResources,
},
{
Path: ObjectPrefix,
Expand Down Expand Up @@ -153,6 +155,15 @@ func (s *Server) createObject(w http.ResponseWriter, r *http.Request) {
return
}

// Validate hooks.
for _, hook := range objectValidateHooks {
err := hook(OperationTypeCreate, spec)
if err != nil {
HandleAPIError(w, r, http.StatusBadRequest, fmt.Errorf("validate failed: %v", err))
return
}
}

s._putObject(spec)
s.upgradeConfigVersion(w, r)

Expand All @@ -179,6 +190,15 @@ func (s *Server) deleteObject(w http.ResponseWriter, r *http.Request) {
return
}

// Validate hooks.
for _, hook := range objectValidateHooks {
err := hook(OperationTypeDelete, spec)
if err != nil {
HandleAPIError(w, r, http.StatusBadRequest, fmt.Errorf("validate failed: %v", err))
return
}
}

s._deleteObject(name)
s.upgradeConfigVersion(w, r)
}
Expand All @@ -191,6 +211,19 @@ func (s *Server) deleteObjects(w http.ResponseWriter, r *http.Request) {

specs := s._listObjects()
for _, spec := range specs {
if spec.Categroy() == supervisor.CategorySystemController {
continue
}

// Validate hooks.
for _, hook := range objectValidateHooks {
err := hook(OperationTypeDelete, spec)
if err != nil {
HandleAPIError(w, r, http.StatusBadRequest, fmt.Errorf("validate failed: %v", err))
return
}
}

s._deleteObject(spec.Name())
}

Expand Down Expand Up @@ -274,6 +307,15 @@ func (s *Server) updateObject(w http.ResponseWriter, r *http.Request) {
return
}

// Validate hooks.
for _, hook := range objectValidateHooks {
err := hook(OperationTypeUpdate, spec)
if err != nil {
HandleAPIError(w, r, http.StatusBadRequest, fmt.Errorf("validate failed: %v", err))
return
}
}

s._putObject(spec)
s.upgradeConfigVersion(w, r)
}
Expand Down Expand Up @@ -341,7 +383,7 @@ func (s *Server) listObjectKinds(w http.ResponseWriter, r *http.Request) {
WriteBody(w, r, kinds)
}

func (s *Server) listObjectApiResources(w http.ResponseWriter, r *http.Request) {
func (s *Server) listObjectAPIResources(w http.ResponseWriter, r *http.Request) {
res := ObjectAPIResources()
WriteBody(w, r, res)
}
36 changes: 30 additions & 6 deletions pkg/api/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,38 @@ package api

import (
"fmt"

"github.com/megaease/easegress/v2/pkg/supervisor"
)

type APIResource struct {
Category string
Kind string
Name string
Aliases []string
}
type (
APIResource struct {
Category string
Kind string
Name string
Aliases []string

// ValiateHook is optional, if set, will be called before create/update/delete object.
// If it returns an error, the operation will be rejected.
ValiateHook ValidateHookFunc `json:"-"`
}

ValidateHookFunc func(operationType OperationType, spec *supervisor.Spec) error

OperationType string
)

const (
OperationTypeCreate OperationType = "create"
OperationTypeUpdate OperationType = "update"
OperationTypeDelete OperationType = "delete"
)

// key is Kind name, now only contains api resource of object.
var objectAPIResource = map[string]*APIResource{}

var objectValidateHooks = []ValidateHookFunc{}

func RegisterObject(r *APIResource) {
if r.Kind == "" {
panic(fmt.Errorf("%v: empty kind", r))
Expand All @@ -45,6 +65,10 @@ func RegisterObject(r *APIResource) {
panic(fmt.Errorf("%v and %v got same kind: %s", r, existedObject, r.Kind))
}
objectAPIResource[r.Kind] = r

if r.ValiateHook != nil {
objectValidateHooks = append(objectValidateHooks, r.ValiateHook)
}
}

func ObjectAPIResources() []*APIResource {
Expand Down
98 changes: 51 additions & 47 deletions pkg/object/autocertmanager/autocertmanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,29 +39,53 @@ import (

const (
// Category is the category of AutoCertManager.
// It is a business controller by now, but should be a system controller.
Category = supervisor.CategoryBusinessController

// Kind is the kind of AutoCertManager.
Kind = "AutoCertManager"
)

var aliases = []string{
"autocert",
"autocerts",
"autocertmanagers",
}
var (
aliases = []string{
"autocert",
"autocerts",
"autocertmanagers",
}

globalACM atomic.Value
)

func init() {
supervisor.Register(&AutoCertManager{})
api.RegisterObject(&api.APIResource{
Category: Category,
Kind: Kind,
Name: strings.ToLower(Kind),
Aliases: aliases,
Category: Category,
Kind: Kind,
Name: strings.ToLower(Kind),
Aliases: aliases,
ValiateHook: validateHook,
})
}

func validateHook(operationType api.OperationType, spec *supervisor.Spec) error {
if operationType != api.OperationTypeCreate || spec.Kind() != Kind {
return nil
}

acms := []string{}
supervisor.GetGlobalSuper().WalkControllers(func(controller *supervisor.ObjectEntity) bool {
if controller.Spec().Kind() == Kind {
acms = append(acms, controller.Spec().Name())
}
return true
})

if len(acms) >= 1 {
return fmt.Errorf("only one AutoCertManager is allowed, existed: %v", acms)
}

return nil
}

type (
// AutoCertManager is the controller for Automated Certificate Management.
AutoCertManager struct {
Expand Down Expand Up @@ -107,8 +131,6 @@ type (
}
)

var globalACM atomic.Value

// Validate validates the spec of AutoCertManager.
func (spec *Spec) Validate() error {
if !(spec.EnableHTTP01 || spec.EnableTLSALPN01 || spec.EnableDNS01) {
Expand Down Expand Up @@ -168,6 +190,7 @@ func (acm *AutoCertManager) DefaultSpec() interface{} {
EnableHTTP01: true,
EnableTLSALPN01: true,
EnableDNS01: true,
Domains: []DomainSpec{},
}
}

Expand All @@ -177,11 +200,6 @@ func (acm *AutoCertManager) Init(superSpec *supervisor.Spec) {
acm.spec = superSpec.ObjectSpec().(*Spec)
acm.super = superSpec.Super()

// TODO: remove this check after converting AutoCertManager to a system controller.
if p := globalACM.Load(); p != nil && p.(*AutoCertManager) != nil {
logger.Warnf("an AutoCertManager instance is already exist")
}

acm.reload()
}

Expand Down Expand Up @@ -242,9 +260,10 @@ func (acm *AutoCertManager) reload() {
d.certificate.Store(cert)
}

globalACM.Store(acm)
go acm.run()
go acm.watchCertificate()

globalACM.Store(acm)
}

// Status returns the status of AutoCertManager.
Expand All @@ -263,10 +282,7 @@ func (acm *AutoCertManager) Status() *supervisor.Status {
// Close closes AutoCertManager.
func (acm *AutoCertManager) Close() {
acm.cancel()
// TODO: remove this after converting AutoCertManager to system controller.
//
// globalACM equals nil means the AutoCertManager is being deleted, so we
// need to set the globalACM to nil.

globalACM.CompareAndSwap(acm, (*AutoCertManager)(nil))
}

Expand Down Expand Up @@ -354,7 +370,8 @@ func (acm *AutoCertManager) run() {
}
}

func (acm *AutoCertManager) getCertificate(chi *tls.ClientHelloInfo, tokenOnly bool) (*tls.Certificate, error) {
// GetCertificate handles the tls hello.
func (acm *AutoCertManager) GetCertificate(chi *tls.ClientHelloInfo, tokenOnly bool) (*tls.Certificate, error) {
name := chi.ServerName
if name == "" {
return nil, fmt.Errorf("missing server name")
Expand Down Expand Up @@ -392,7 +409,8 @@ func (acm *AutoCertManager) getCertificate(chi *tls.ClientHelloInfo, tokenOnly b
return cert, nil
}

func (acm *AutoCertManager) handleHTTP01Challenge(w http.ResponseWriter, r *http.Request) {
// HandleHTTP01Challenge handles HTTP-01 challenge.
func (acm *AutoCertManager) HandleHTTP01Challenge(w http.ResponseWriter, r *http.Request) {
if !acm.spec.EnableHTTP01 {
http.Error(w, "HTTP01 challenge is disabled", http.StatusNotFound)
return
Expand All @@ -413,31 +431,17 @@ func (acm *AutoCertManager) handleHTTP01Challenge(w http.ResponseWriter, r *http
w.Write(data)
}

// GetCertificate handles the tls hello
func GetCertificate(chi *tls.ClientHelloInfo, tokenOnly bool) (*tls.Certificate, error) {
var acm *AutoCertManager
if p := globalACM.Load(); p != nil {
acm = p.(*AutoCertManager)
}
if acm != nil {
return acm.getCertificate(chi, tokenOnly)
func GetGlobalAutoCertManager() (*AutoCertManager, bool) {
value := globalACM.Load()
if value == nil {
return nil, false
}

// return a nil error if the AutoCertManager is not started, otherwise:
// * static certificates configured in an HTTP server are never used, which is a bug
// * the Go HTTP package logs a lot of 'TLS handshake error'
return nil, nil
}
acm := value.(*AutoCertManager)

// HandleHTTP01Challenge handles HTTP-01 challenge
func HandleHTTP01Challenge(w http.ResponseWriter, r *http.Request) {
var acm *AutoCertManager
if p := globalACM.Load(); p != nil {
acm = p.(*AutoCertManager)
}
if acm != nil {
acm.handleHTTP01Challenge(w, r)
} else {
http.Error(w, "auto certificate manager is not started", http.StatusNotFound)
if acm == nil {
return nil, false
}

return acm, true
}
Loading

0 comments on commit c3ab195

Please sign in to comment.