Merge pull request #5736 from irbekrm/webhook_solver_conformance_bugfix
Webhook solver conformance bugfix
This commit is contained in:
commit
c08b337cf7
@ -112,7 +112,10 @@ func (c *Config) Complete() CompletedConfig {
|
||||
return CompletedConfig{&completedCfg}
|
||||
}
|
||||
|
||||
// New returns a new instance of AdmissionServer from the given config.
|
||||
// New returns a new instance of apiserver from the given config. Each of the
|
||||
// configured solvers will have an API GroupVersion registered with the new
|
||||
// apiserver and will have its Initialize function passed as post-start hook
|
||||
// with the server.
|
||||
func (c completedConfig) New() (*ChallengeServer, error) {
|
||||
genericServer, err := c.GenericConfig.New("challenge-server", genericapiserver.NewEmptyDelegate()) // completion is done in Complete, no need for a second time
|
||||
if err != nil {
|
||||
|
||||
@ -29,6 +29,11 @@ import (
|
||||
logf "github.com/cert-manager/cert-manager/pkg/logs"
|
||||
)
|
||||
|
||||
// RunWebhookServer creates and starts a new apiserver that acts as a external
|
||||
// webhook server for solving DNS challenges using the provided solver
|
||||
// implementations. This can be used as an entry point by external webhook
|
||||
// implementations, see
|
||||
// https://github.com/cert-manager/webhook-example/blob/899c408751425f8d0842b61c0e62fd8035d00316/main.go#L23-L31
|
||||
func RunWebhookServer(groupName string, hooks ...webhook.Solver) {
|
||||
stopCh, exit := util.SetupExitHandler(util.GracefulShutdown)
|
||||
defer exit() // This function might call os.Exit, so defer last
|
||||
|
||||
@ -97,6 +97,9 @@ func (o *WebhookServerOptions) Complete() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Config creates a new webhook server config that includes generic upstream
|
||||
// apiserver options, rest client config and the Solvers configured for this
|
||||
// webhook server
|
||||
func (o WebhookServerOptions) Config() (*apiserver.Config, error) {
|
||||
// TODO have a "real" external address
|
||||
if err := o.RecommendedOptions.SecureServing.MaybeDefaultWithSelfSignedCerts("localhost", nil, []net.IP{net.ParseIP("127.0.0.1")}); err != nil {
|
||||
@ -118,6 +121,8 @@ func (o WebhookServerOptions) Config() (*apiserver.Config, error) {
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// RunWebhookServer creates a new apiserver, registers an API Group for each of
|
||||
// the configured solvers and runs the new apiserver.
|
||||
func (o WebhookServerOptions) RunWebhookServer(stopCh <-chan struct{}) error {
|
||||
config, err := o.Config()
|
||||
if err != nil {
|
||||
|
||||
@ -24,7 +24,9 @@ import (
|
||||
whapi "github.com/cert-manager/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
|
||||
)
|
||||
|
||||
// Solver has the functionality to solve ACME challenges.
|
||||
// Solver has the functionality to solve ACME challenges. This interface is
|
||||
// implemented internally by RFC2136 DNS provider and by external webhook solver
|
||||
// implementations see https://github.com/cert-manager/webhook-example
|
||||
type Solver interface {
|
||||
// Name is the name of this ACME solver as part of the API group.
|
||||
// This must match what you configure in the ACME Issuer's DNS01 config.
|
||||
@ -41,5 +43,6 @@ type Solver interface {
|
||||
CleanUp(ch *whapi.ChallengeRequest) error
|
||||
|
||||
// Initialize is called as a post-start hook when the apiserver starts.
|
||||
// https://github.com/kubernetes/apiserver/blob/release-1.26/pkg/server/hooks.go#L32-L42
|
||||
Initialize(kubeClientConfig *restclient.Config, stopCh <-chan struct{}) error
|
||||
}
|
||||
|
||||
@ -37,6 +37,8 @@ const SolverName = "rfc2136"
|
||||
|
||||
type Solver struct {
|
||||
secretLister corelisters.SecretLister
|
||||
// options to apply when the lister gets initialized
|
||||
initOpts []Option
|
||||
|
||||
// If specified, namespace will cause the rfc2136 provider to limit the
|
||||
// scope of the lister/watcher to a single namespace, to allow for
|
||||
@ -58,6 +60,21 @@ func WithSecretsLister(secretLister corelisters.SecretLister) Option {
|
||||
}
|
||||
}
|
||||
|
||||
// InitializeResetLister is a hack to make RFC2136 solver fit the Solver
|
||||
// interface. Unlike external solvers that are run as apiserver implementations,
|
||||
// this solver is created as part of challenge controller initialization. That
|
||||
// makes its Initialize method not fit the Solver interface very well as we want
|
||||
// a way to initialize the solver with the existing Secrets lister rather than a
|
||||
// new kube apiserver client. InitializeResetLister allows to reset secrets
|
||||
// lister when Initialize function is called so that a new lister can be
|
||||
// created. This is useful in tests where a kube clientset can get recreated for
|
||||
// an existing solver (which would not happen when this solver runs normally).
|
||||
func InitializeResetLister() Option {
|
||||
return func(s *Solver) {
|
||||
s.initOpts = []Option{func(s *Solver) { s.secretLister = nil }}
|
||||
}
|
||||
}
|
||||
|
||||
func New(opts ...Option) *Solver {
|
||||
s := &Solver{}
|
||||
for _, o := range opts {
|
||||
@ -99,12 +116,12 @@ func (s *Solver) CleanUp(ch *whapi.ChallengeRequest) error {
|
||||
}
|
||||
|
||||
func (s *Solver) Initialize(kubeClientConfig *restclient.Config, stopCh <-chan struct{}) error {
|
||||
for _, opt := range s.initOpts {
|
||||
opt(s)
|
||||
}
|
||||
// Only start a secrets informerfactory if it is needed (if the solver
|
||||
// is not already initialized with a secrets lister) This is legacy
|
||||
// functionality. If you have a secrets watcher already available in the
|
||||
// caller, you probably want to use that to avoid double caching the
|
||||
// Secrets
|
||||
// TODO: refactor and remove this functionality
|
||||
// functionality and is currently only used in integration tests.
|
||||
if s.secretLister == nil {
|
||||
cl, err := kubernetes.NewForConfig(kubeClientConfig)
|
||||
if err != nil {
|
||||
|
||||
@ -24,12 +24,10 @@ import (
|
||||
"time"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
"k8s.io/client-go/informers"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"sigs.k8s.io/controller-runtime/pkg/envtest"
|
||||
|
||||
"github.com/cert-manager/cert-manager/pkg/acme/webhook"
|
||||
"github.com/cert-manager/cert-manager/pkg/issuer/acme/dns/rfc2136"
|
||||
"github.com/cert-manager/cert-manager/test/internal/apiserver"
|
||||
)
|
||||
|
||||
@ -44,9 +42,7 @@ func init() {
|
||||
type fixture struct {
|
||||
// testSolver is the actual DNS solver that is under test.
|
||||
// It is set when calling the NewFixture function.
|
||||
testSolver webhook.Solver
|
||||
testSolverType string
|
||||
|
||||
testSolver webhook.Solver
|
||||
resolvedFQDN string
|
||||
resolvedZone string
|
||||
allowAmbientCredentials bool
|
||||
@ -81,55 +77,10 @@ type fixture struct {
|
||||
propagationLimit time.Duration
|
||||
}
|
||||
|
||||
func (f *fixture) setup(t *testing.T) func() {
|
||||
f.setupLock.Lock()
|
||||
defer f.setupLock.Unlock()
|
||||
|
||||
if err := validate(f); err != nil {
|
||||
t.Fatalf("error validating test fixture configuration: %v", err)
|
||||
}
|
||||
|
||||
env, stopFunc := apiserver.RunBareControlPlane(t)
|
||||
f.environment = env
|
||||
|
||||
cl, err := kubernetes.NewForConfig(env.Config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.clientset = cl
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
var testSolver webhook.Solver
|
||||
switch f.testSolverType {
|
||||
case rfc2136.SolverName:
|
||||
cl, err := kubernetes.NewForConfig(env.Config)
|
||||
if err != nil {
|
||||
t.Errorf("error initializing solver: %#+v", err)
|
||||
}
|
||||
|
||||
// obtain a secret lister and start the informer factory to populate the
|
||||
// secret cache
|
||||
factory := informers.NewSharedInformerFactoryWithOptions(cl, time.Minute*5)
|
||||
secretLister := factory.Core().V1().Secrets().Lister()
|
||||
factory.Start(stopCh)
|
||||
factory.WaitForCacheSync(stopCh)
|
||||
testSolver = rfc2136.New(rfc2136.WithSecretsLister(secretLister))
|
||||
f.testSolver = testSolver
|
||||
default:
|
||||
t.Errorf("unknown solver type: %s", f.testSolverType)
|
||||
}
|
||||
|
||||
testSolver.Initialize(env.Config, stopCh)
|
||||
|
||||
return func() {
|
||||
close(stopCh)
|
||||
stopFunc()
|
||||
}
|
||||
}
|
||||
|
||||
// RunConformance will execute all conformance tests using the supplied
|
||||
// configuration
|
||||
// configuration These conformance tests should be run by all external DNS
|
||||
// solver webhook implementations, see
|
||||
// https://github.com/cert-manager/webhook-example
|
||||
func (f *fixture) RunConformance(t *testing.T) {
|
||||
defer f.setup(t)()
|
||||
t.Run("Conformance", func(t *testing.T) {
|
||||
@ -151,3 +102,30 @@ func (f *fixture) RunExtended(t *testing.T) {
|
||||
t.Run("DeletingOneRecordRetainsOthers", f.TestExtendedDeletingOneRecordRetainsOthers)
|
||||
})
|
||||
}
|
||||
|
||||
func (f *fixture) setup(t *testing.T) func() {
|
||||
f.setupLock.Lock()
|
||||
defer f.setupLock.Unlock()
|
||||
|
||||
if err := validate(f); err != nil {
|
||||
t.Fatalf("error validating test fixture configuration: %v", err)
|
||||
}
|
||||
|
||||
env, stopFunc := apiserver.RunBareControlPlane(t)
|
||||
f.environment = env
|
||||
|
||||
cl, err := kubernetes.NewForConfig(env.Config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.clientset = cl
|
||||
|
||||
stopCh := make(chan struct{})
|
||||
|
||||
f.testSolver.Initialize(env.Config, stopCh)
|
||||
|
||||
return func() {
|
||||
close(stopCh)
|
||||
stopFunc()
|
||||
}
|
||||
}
|
||||
|
||||
@ -24,16 +24,21 @@ import (
|
||||
"time"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
|
||||
"github.com/cert-manager/cert-manager/pkg/acme/webhook"
|
||||
)
|
||||
|
||||
// Option applies a configuration option to the test fixture being built
|
||||
type Option func(*fixture)
|
||||
|
||||
// NewFixture constructs a new *fixture, applying the given Options before
|
||||
// returning.
|
||||
func NewFixture(solverType string, opts ...Option) *fixture {
|
||||
// returning. Solver is an implementation of
|
||||
// https://github.com/cert-manager/cert-manager/blob/v1.11.0/pkg/acme/webhook/webhook.go#L27-L45
|
||||
// and could be RFC2136 solver or any of external solvers that run these
|
||||
// conformance tests.
|
||||
func NewFixture(solver webhook.Solver, opts ...Option) *fixture {
|
||||
f := &fixture{
|
||||
testSolverType: solverType,
|
||||
testSolver: solver,
|
||||
}
|
||||
for _, o := range opts {
|
||||
o(f)
|
||||
|
||||
@ -59,7 +59,7 @@ func TestRunSuiteWithTSIG(t *testing.T) {
|
||||
TSIGKeyName: rfc2136TestTsigKeyName,
|
||||
}
|
||||
|
||||
fixture := dns.NewFixture(rfc2136.SolverName,
|
||||
fixture := dns.NewFixture(rfc2136.New(rfc2136.InitializeResetLister()),
|
||||
dns.SetResolvedZone(rfc2136TestZone),
|
||||
dns.SetResolvedFQDN(rfc2136TestFqdn),
|
||||
dns.SetAllowAmbientCredentials(false),
|
||||
@ -91,7 +91,7 @@ func TestRunSuiteNoTSIG(t *testing.T) {
|
||||
Nameserver: server.ListenAddr(),
|
||||
}
|
||||
|
||||
fixture := dns.NewFixture(rfc2136.SolverName,
|
||||
fixture := dns.NewFixture(rfc2136.New(rfc2136.InitializeResetLister()),
|
||||
dns.SetResolvedZone(rfc2136TestZone),
|
||||
dns.SetResolvedFQDN(rfc2136TestFqdn),
|
||||
dns.SetAllowAmbientCredentials(false),
|
||||
|
||||
Loading…
Reference in New Issue
Block a user