Merge pull request #1563 from munnerz/webhook-dns-provider

Add webhook dns provider implementation
This commit is contained in:
jetstack-bot
2019-04-18 18:59:04 +01:00
committed by GitHub
12 changed files with 460 additions and 18 deletions
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
(function(){navData = {"toc":[{"section":"-strong-field-definitions-strong-","subsections":[{"section":"venafitpp-v1alpha1"},{"section":"venafiissuer-v1alpha1"},{"section":"venaficloud-v1alpha1"},{"section":"vaultissuer-v1alpha1"},{"section":"vaultauth-v1alpha1"},{"section":"vaultapprole-v1alpha1"},{"section":"time-v1"},{"section":"statusdetails-v1"},{"section":"statuscause-v1"},{"section":"status-v1"},{"section":"solverconfig-v1alpha1"},{"section":"selfsignedissuer-v1alpha1"},{"section":"secretkeyselector-v1alpha1"},{"section":"ownerreference-v1"},{"section":"objectreference-v1alpha1"},{"section":"objectmeta-v1"},{"section":"managedfieldsentry-v1"},{"section":"localobjectreference-v1alpha1"},{"section":"listmeta-v1"},{"section":"issuercondition-v1alpha1"},{"section":"initializers-v1"},{"section":"initializer-v1"},{"section":"http01solverconfig-v1alpha1"},{"section":"fields-v1"},{"section":"duration-v1"},{"section":"domainsolverconfig-v1alpha1"},{"section":"dns01solverconfig-v1alpha1"},{"section":"certificatecondition-v1alpha1"},{"section":"caissuer-v1alpha1"},{"section":"acmeissuerhttp01config-v1alpha1"},{"section":"acmeissuerdns01providerroute53-v1alpha1"},{"section":"acmeissuerdns01providerrfc2136-v1alpha1"},{"section":"acmeissuerdns01providerdigitalocean-v1alpha1"},{"section":"acmeissuerdns01providercloudflare-v1alpha1"},{"section":"acmeissuerdns01providerclouddns-v1alpha1"},{"section":"acmeissuerdns01providerazuredns-v1alpha1"},{"section":"acmeissuerdns01providerakamai-v1alpha1"},{"section":"acmeissuerdns01provideracmedns-v1alpha1"},{"section":"acmeissuerdns01provider-v1alpha1"},{"section":"acmeissuerdns01config-v1alpha1"},{"section":"acmeissuer-v1alpha1"},{"section":"acmecertificateconfig-v1alpha1"}]},{"section":"-strong-old-api-versions-strong-","subsections":[]},{"section":"challenge-v1alpha1","subsections":[]},{"section":"order-v1alpha1","subsections":[]},{"section":"issuer-v1alpha1","subsections":[]},{"section":"clusterissuer-v1alpha1","subsections":[]},{"section":"certificate-v1alpha1","subsections":[]},{"section":"-strong-cert-manager-strong-","subsections":[]}],"flatToc":["venafitpp-v1alpha1","venafiissuer-v1alpha1","venaficloud-v1alpha1","vaultissuer-v1alpha1","vaultauth-v1alpha1","vaultapprole-v1alpha1","time-v1","statusdetails-v1","statuscause-v1","status-v1","solverconfig-v1alpha1","selfsignedissuer-v1alpha1","secretkeyselector-v1alpha1","ownerreference-v1","objectreference-v1alpha1","objectmeta-v1","managedfieldsentry-v1","localobjectreference-v1alpha1","listmeta-v1","issuercondition-v1alpha1","initializers-v1","initializer-v1","http01solverconfig-v1alpha1","fields-v1","duration-v1","domainsolverconfig-v1alpha1","dns01solverconfig-v1alpha1","certificatecondition-v1alpha1","caissuer-v1alpha1","acmeissuerhttp01config-v1alpha1","acmeissuerdns01providerroute53-v1alpha1","acmeissuerdns01providerrfc2136-v1alpha1","acmeissuerdns01providerdigitalocean-v1alpha1","acmeissuerdns01providercloudflare-v1alpha1","acmeissuerdns01providerclouddns-v1alpha1","acmeissuerdns01providerazuredns-v1alpha1","acmeissuerdns01providerakamai-v1alpha1","acmeissuerdns01provideracmedns-v1alpha1","acmeissuerdns01provider-v1alpha1","acmeissuerdns01config-v1alpha1","acmeissuer-v1alpha1","acmecertificateconfig-v1alpha1","-strong-field-definitions-strong-","-strong-old-api-versions-strong-","challenge-v1alpha1","order-v1alpha1","issuer-v1alpha1","clusterissuer-v1alpha1","certificate-v1alpha1","-strong-cert-manager-strong-"]};})();
(function(){navData = {"toc":[{"section":"-strong-field-definitions-strong-","subsections":[{"section":"venafitpp-v1alpha1"},{"section":"venafiissuer-v1alpha1"},{"section":"venaficloud-v1alpha1"},{"section":"vaultissuer-v1alpha1"},{"section":"vaultauth-v1alpha1"},{"section":"vaultapprole-v1alpha1"},{"section":"time-v1"},{"section":"statusdetails-v1"},{"section":"statuscause-v1"},{"section":"status-v1"},{"section":"solverconfig-v1alpha1"},{"section":"selfsignedissuer-v1alpha1"},{"section":"secretkeyselector-v1alpha1"},{"section":"ownerreference-v1"},{"section":"objectreference-v1alpha1"},{"section":"objectmeta-v1"},{"section":"managedfieldsentry-v1"},{"section":"localobjectreference-v1alpha1"},{"section":"listmeta-v1"},{"section":"issuercondition-v1alpha1"},{"section":"initializers-v1"},{"section":"initializer-v1"},{"section":"http01solverconfig-v1alpha1"},{"section":"fields-v1"},{"section":"duration-v1"},{"section":"domainsolverconfig-v1alpha1"},{"section":"dns01solverconfig-v1alpha1"},{"section":"certificatecondition-v1alpha1"},{"section":"caissuer-v1alpha1"},{"section":"acmeissuerhttp01config-v1alpha1"},{"section":"acmeissuerdns01providerwebhook-v1alpha1"},{"section":"acmeissuerdns01providerroute53-v1alpha1"},{"section":"acmeissuerdns01providerrfc2136-v1alpha1"},{"section":"acmeissuerdns01providerdigitalocean-v1alpha1"},{"section":"acmeissuerdns01providercloudflare-v1alpha1"},{"section":"acmeissuerdns01providerclouddns-v1alpha1"},{"section":"acmeissuerdns01providerazuredns-v1alpha1"},{"section":"acmeissuerdns01providerakamai-v1alpha1"},{"section":"acmeissuerdns01provideracmedns-v1alpha1"},{"section":"acmeissuerdns01provider-v1alpha1"},{"section":"acmeissuerdns01config-v1alpha1"},{"section":"acmeissuer-v1alpha1"},{"section":"acmecertificateconfig-v1alpha1"}]},{"section":"-strong-old-api-versions-strong-","subsections":[]},{"section":"challenge-v1alpha1","subsections":[]},{"section":"order-v1alpha1","subsections":[]},{"section":"issuer-v1alpha1","subsections":[]},{"section":"clusterissuer-v1alpha1","subsections":[]},{"section":"certificate-v1alpha1","subsections":[]},{"section":"-strong-cert-manager-strong-","subsections":[]}],"flatToc":["venafitpp-v1alpha1","venafiissuer-v1alpha1","venaficloud-v1alpha1","vaultissuer-v1alpha1","vaultauth-v1alpha1","vaultapprole-v1alpha1","time-v1","statusdetails-v1","statuscause-v1","status-v1","solverconfig-v1alpha1","selfsignedissuer-v1alpha1","secretkeyselector-v1alpha1","ownerreference-v1","objectreference-v1alpha1","objectmeta-v1","managedfieldsentry-v1","localobjectreference-v1alpha1","listmeta-v1","issuercondition-v1alpha1","initializers-v1","initializer-v1","http01solverconfig-v1alpha1","fields-v1","duration-v1","domainsolverconfig-v1alpha1","dns01solverconfig-v1alpha1","certificatecondition-v1alpha1","caissuer-v1alpha1","acmeissuerhttp01config-v1alpha1","acmeissuerdns01providerwebhook-v1alpha1","acmeissuerdns01providerroute53-v1alpha1","acmeissuerdns01providerrfc2136-v1alpha1","acmeissuerdns01providerdigitalocean-v1alpha1","acmeissuerdns01providercloudflare-v1alpha1","acmeissuerdns01providerclouddns-v1alpha1","acmeissuerdns01providerazuredns-v1alpha1","acmeissuerdns01providerakamai-v1alpha1","acmeissuerdns01provideracmedns-v1alpha1","acmeissuerdns01provider-v1alpha1","acmeissuerdns01config-v1alpha1","acmeissuer-v1alpha1","acmecertificateconfig-v1alpha1","-strong-field-definitions-strong-","-strong-old-api-versions-strong-","challenge-v1alpha1","order-v1alpha1","issuer-v1alpha1","clusterissuer-v1alpha1","certificate-v1alpha1","-strong-cert-manager-strong-"]};})();
@@ -22,6 +22,7 @@ go_library(
deps = [
"//pkg/apis/certmanager:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
+34 -3
View File
@@ -18,6 +18,7 @@ package v1alpha1
import (
corev1 "k8s.io/api/core/v1"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@@ -86,7 +87,9 @@ type IssuerConfig struct {
// +optional
SelfSigned *SelfSignedIssuer `json:"selfSigned,omitempty"`
Venafi *VenafiIssuer `json:"venafi,omitempty"`
// +optional
Venafi *VenafiIssuer `json:"venafi,omitempty"`
}
// VenafiIssuer describes issuer configuration details for Venafi Cloud.
@@ -137,8 +140,7 @@ type VenafiCloud struct {
APITokenSecretRef SecretKeySelector `json:"apiTokenSecretRef"`
}
type SelfSignedIssuer struct {
}
type SelfSignedIssuer struct{}
type VaultIssuer struct {
// Vault authentication
@@ -261,6 +263,9 @@ type ACMEIssuerDNS01Provider struct {
// +optional
RFC2136 *ACMEIssuerDNS01ProviderRFC2136 `json:"rfc2136,omitempty"`
// +optional
Webhook *ACMEIssuerDNS01ProviderWebhook `json:"webhook,omitempty"`
}
// CNAMEStrategy configures how the DNS01 provider should handle CNAME records
@@ -373,6 +378,32 @@ type ACMEIssuerDNS01ProviderRFC2136 struct {
TSIGAlgorithm string `json:"tsigAlgorithm,omitempty"`
}
// ACMEIssuerDNS01ProviderWebhook specifies configuration for a webhook DNS01
// provider, including where to POST ChallengePayload resources.
type ACMEIssuerDNS01ProviderWebhook struct {
// The API group name that should be used when POSTing ChallengePayload
// resources to the webhook apiserver.
// This should be the same as the GroupName specified in the webhook
// provider implementation.
GroupName string `json:"groupName"`
// The name of the solver to use, as defined in the webhook provider
// implementation.
// This will typically be the name of the provider, e.g. 'cloudflare'.
SolverName string `json:"solverName"`
// Additional configuration that should be passed to the webhook apiserver
// when challenges are processed.
// This can contain arbitrary JSON data.
// Secret values should not be specified in this stanza.
// If secret values are needed (e.g. credentials for a DNS service), you
// should use a SecretKeySelector to reference a Secret resource.
// For details on the schema of this field, consult the webhook provider
// implementation's documentation.
// +optional
Config *apiext.JSON `json:"config,omitempty"`
}
// IssuerStatus contains status information about an Issuer
type IssuerStatus struct {
// +optional
@@ -21,6 +21,7 @@ limitations under the License.
package v1alpha1
import (
v1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@@ -141,6 +142,11 @@ func (in *ACMEIssuerDNS01Provider) DeepCopyInto(out *ACMEIssuerDNS01Provider) {
*out = new(ACMEIssuerDNS01ProviderRFC2136)
**out = **in
}
if in.Webhook != nil {
in, out := &in.Webhook, &out.Webhook
*out = new(ACMEIssuerDNS01ProviderWebhook)
(*in).DeepCopyInto(*out)
}
return
}
@@ -292,6 +298,27 @@ func (in *ACMEIssuerDNS01ProviderRoute53) DeepCopy() *ACMEIssuerDNS01ProviderRou
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopyInto(out *ACMEIssuerDNS01ProviderWebhook) {
*out = *in
if in.Config != nil {
in, out := &in.Config, &out.Config
*out = new(v1beta1.JSON)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ACMEIssuerDNS01ProviderWebhook.
func (in *ACMEIssuerDNS01ProviderWebhook) DeepCopy() *ACMEIssuerDNS01ProviderWebhook {
if in == nil {
return nil
}
out := new(ACMEIssuerDNS01ProviderWebhook)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ACMEIssuerHTTP01Config) DeepCopyInto(out *ACMEIssuerHTTP01Config) {
*out = *in
@@ -43,6 +43,7 @@ go_test(
"//pkg/acme/client:go_default_library",
"//pkg/apis/certmanager/v1alpha1:go_default_library",
"//pkg/controller/test:go_default_library",
"//pkg/issuer/acme/dns:go_default_library",
"//test/unit/gen:go_default_library",
"//third_party/crypto/acme:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
+5 -1
View File
@@ -106,7 +106,11 @@ func New(ctx *controllerpkg.Context) (*Controller, error) {
ctrl.acmeHelper = acme.NewHelper(ctrl.secretLister, ctrl.Context.ClusterResourceNamespace)
ctrl.httpSolver = http.NewSolver(ctx)
ctrl.dnsSolver = dns.NewSolver(ctx)
var err error
ctrl.dnsSolver, err = dns.NewSolver(ctx)
if err != nil {
return nil, err
}
ctrl.scheduler = scheduler.New(ctrl.challengeLister)
ctrl.ctx = logf.NewContext(ctx.RootContext, nil, ControllerName)
@@ -27,6 +27,7 @@ import (
acmecl "github.com/jetstack/cert-manager/pkg/acme/client"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
testpkg "github.com/jetstack/cert-manager/pkg/controller/test"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns"
"github.com/jetstack/cert-manager/test/unit/gen"
acmeapi "github.com/jetstack/cert-manager/third_party/crypto/acme"
)
@@ -323,6 +324,10 @@ func TestSyncHappyPath(t *testing.T) {
if test.Builder == nil {
test.Builder = &testpkg.Builder{}
}
// Don't initialise webhook based DNS solvers during tests as we do
// not have a valid RESTConfig that can be used in the Initialize
// functions.
dns.WebhookSolvers = nil
test.Setup(t)
chalCopy := test.Challenge.DeepCopy()
err := test.Controller.Sync(test.Ctx, chalCopy)
+5
View File
@@ -6,6 +6,8 @@ go_library(
importpath = "github.com/jetstack/cert-manager/pkg/issuer/acme/dns",
visibility = ["//visibility:public"],
deps = [
"//pkg/acme/webhook:go_default_library",
"//pkg/acme/webhook/apis/acme/v1alpha1:go_default_library",
"//pkg/apis/certmanager/v1alpha1:go_default_library",
"//pkg/controller:go_default_library",
"//pkg/issuer/acme/dns/acmedns:go_default_library",
@@ -17,7 +19,9 @@ go_library(
"//pkg/issuer/acme/dns/rfc2136:go_default_library",
"//pkg/issuer/acme/dns/route53:go_default_library",
"//pkg/issuer/acme/dns/util:go_default_library",
"//pkg/issuer/acme/dns/webhook:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/listers/core/v1:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
@@ -69,6 +73,7 @@ filegroup(
"//pkg/issuer/acme/dns/rfc2136:all-srcs",
"//pkg/issuer/acme/dns/route53:all-srcs",
"//pkg/issuer/acme/dns/util:all-srcs",
"//pkg/issuer/acme/dns/webhook:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
+127 -12
View File
@@ -18,14 +18,18 @@ package dns
import (
"context"
"encoding/json"
"fmt"
"strings"
"time"
"github.com/pkg/errors"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
corev1listers "k8s.io/client-go/listers/core/v1"
"k8s.io/klog"
"github.com/jetstack/cert-manager/pkg/acme/webhook"
whapi "github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
"github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/controller"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/acmedns"
@@ -37,12 +41,15 @@ import (
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/rfc2136"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/route53"
"github.com/jetstack/cert-manager/pkg/issuer/acme/dns/util"
webhookslv "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/webhook"
)
const (
cloudDNSServiceAccountKey = "service-account.json"
)
// solver is the old solver type interface.
// All new solvers should be implemented using the new webhook.Solver interface.
type solver interface {
Present(domain, fqdn, value string) error
CleanUp(domain, fqdn, value string) error
@@ -68,6 +75,7 @@ type Solver struct {
*controller.Context
secretLister corev1listers.SecretLister
dnsProviderConstructors dnsProviderConstructors
webhookSolvers map[string]webhook.Solver
}
// Present performs the work to configure DNS to resolve a DNS01 challenge.
@@ -76,6 +84,15 @@ func (s *Solver) Present(ctx context.Context, issuer v1alpha1.GenericIssuer, ch
return fmt.Errorf("challenge dns config must be specified")
}
webhookSolver, req, err := s.prepareChallengeRequest(issuer, ch)
if err != nil && err != errNotFound {
return err
}
if err == nil {
klog.Infof("Presenting DNS01 challenge for domain %q", ch.Spec.DNSName)
return webhookSolver.Present(req)
}
slv, providerConfig, err := s.solverForChallenge(issuer, ch)
if err != nil {
return err
@@ -123,6 +140,15 @@ func (s *Solver) CleanUp(ctx context.Context, issuer v1alpha1.GenericIssuer, ch
return fmt.Errorf("challenge dns config must be specified")
}
webhookSolver, req, err := s.prepareChallengeRequest(issuer, ch)
if err != nil && err != errNotFound {
return err
}
if err == nil {
klog.Infof("Cleaning up DNS01 challenge for domain %q", ch.Spec.DNSName)
return webhookSolver.CleanUp(req)
}
slv, providerConfig, err := s.solverForChallenge(issuer, ch)
if err != nil {
return err
@@ -150,12 +176,7 @@ func (s *Solver) solverForChallenge(issuer v1alpha1.GenericIssuer, ch *v1alpha1.
resourceNamespace := s.ResourceNamespace(issuer)
canUseAmbientCredentials := s.CanUseAmbientCredentials(issuer)
providerName := ch.Spec.Config.DNS01.Provider
if providerName == "" {
return nil, nil, fmt.Errorf("dns01 challenge provider name must be set")
}
providerConfig, err := issuer.GetSpec().ACME.DNS01.Provider(providerName)
providerConfig, err := s.dns01ConfigForChallenge(issuer, ch)
if err != nil {
return nil, nil, err
}
@@ -337,19 +358,112 @@ func (s *Solver) solverForChallenge(issuer v1alpha1.GenericIssuer, ch *v1alpha1.
return nil, nil, fmt.Errorf("error instantiating rfc2136 challenge solver: %s", err.Error())
}
default:
return nil, nil, fmt.Errorf("no dns provider config specified for provider %q", providerName)
return nil, nil, fmt.Errorf("no dns provider config specified for provider %q", ch.Spec.Config.DNS01.Provider)
}
return impl, providerConfig, nil
}
func (s *Solver) prepareChallengeRequest(issuer v1alpha1.GenericIssuer, ch *v1alpha1.Challenge) (webhook.Solver, *whapi.ChallengeRequest, error) {
dns01Config, err := s.dns01ConfigForChallenge(issuer, ch)
if err != nil {
return nil, nil, err
}
webhookSolver, cfg, err := s.dns01SolverForConfig(dns01Config)
if err != nil {
return nil, nil, err
}
fqdn, err := util.DNS01LookupFQDN(ch.Spec.DNSName, followCNAME(dns01Config.CNAMEStrategy), s.DNS01Nameservers...)
if err != nil {
return nil, nil, err
}
zone, err := util.FindZoneByFqdn(fqdn, s.DNS01Nameservers)
if err != nil {
return nil, nil, err
}
resourceNamespace := s.ResourceNamespace(issuer)
canUseAmbientCredentials := s.CanUseAmbientCredentials(issuer)
// construct a ChallengeRequest which can be passed to DNS solvers.
// The provided config will be encoded to JSON in order to avoid a coupling
// between cert-manager and any particular DNS provider implementation.
b, err := json.Marshal(cfg)
if err != nil {
return nil, nil, err
}
req := &whapi.ChallengeRequest{
Type: "dns-01",
ResolvedFQDN: fqdn,
ResolvedZone: zone,
AllowAmbientCredentials: canUseAmbientCredentials,
ResourceNamespace: resourceNamespace,
Key: ch.Spec.Key,
Config: &apiext.JSON{Raw: b},
}
return webhookSolver, req, nil
}
var errNotFound = fmt.Errorf("failed to determine DNS01 solver type")
func (s *Solver) dns01SolverForConfig(config *v1alpha1.ACMEIssuerDNS01Provider) (webhook.Solver, interface{}, error) {
solverName := ""
var c interface{}
switch {
case config.Webhook != nil:
solverName = "webhook"
c = config.Webhook
}
if solverName == "" {
return nil, nil, errNotFound
}
p := s.webhookSolvers[solverName]
if p == nil {
return nil, c, fmt.Errorf("no solver provider configured for %q", solverName)
}
return p, c, nil
}
func (s *Solver) dns01ConfigForChallenge(issuer v1alpha1.GenericIssuer, ch *v1alpha1.Challenge) (*v1alpha1.ACMEIssuerDNS01Provider, error) {
providerName := ch.Spec.Config.DNS01.Provider
if providerName == "" {
return nil, fmt.Errorf("dns01 challenge provider name must be set")
}
dns01Config, err := issuer.GetSpec().ACME.DNS01.Provider(providerName)
if err != nil {
return nil, err
}
return dns01Config, nil
}
var WebhookSolvers = []webhook.Solver{
&webhookslv.Webhook{},
}
// NewSolver creates a Solver which can instantiate the appropriate DNS
// provider.
func NewSolver(ctx *controller.Context) *Solver {
func NewSolver(ctx *controller.Context) (*Solver, error) {
initialized := make(map[string]webhook.Solver)
// initialize all DNS providers
for _, s := range WebhookSolvers {
err := s.Initialize(ctx.RESTConfig, ctx.StopCh)
if err != nil {
return nil, fmt.Errorf("error intializing DNS provider %q: %v", s.Name(), err)
}
initialized[s.Name()] = s
}
return &Solver{
ctx,
ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
dnsProviderConstructors{
Context: ctx,
secretLister: ctx.KubeSharedInformerFactory.Core().V1().Secrets().Lister(),
dnsProviderConstructors: dnsProviderConstructors{
clouddns.NewDNSProvider,
cloudflare.NewDNSProviderCredentials,
route53.NewDNSProvider,
@@ -358,7 +472,8 @@ func NewSolver(ctx *controller.Context) *Solver {
rfc2136.NewDNSProviderCredentials,
digitalocean.NewDNSProviderCredentials,
},
}
webhookSolvers: initialized,
}, nil
}
func (s *Solver) loadSecretData(selector *v1alpha1.SecretKeySelector, ns string) ([]byte, error) {
+33
View File
@@ -0,0 +1,33 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = ["webhook.go"],
importpath = "github.com/jetstack/cert-manager/pkg/issuer/acme/dns/webhook",
visibility = ["//visibility:public"],
deps = [
"//pkg/acme/webhook/apis/acme/v1alpha1:go_default_library",
"//pkg/apis/certmanager/v1alpha1:go_default_library",
"//pkg/client/clientset/versioned/scheme:go_default_library",
"//vendor/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
"//vendor/k8s.io/client-go/rest:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
+169
View File
@@ -0,0 +1,169 @@
/*
Copyright 2019 The Jetstack cert-manager contributors.
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
http://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 webhook
import (
"encoding/json"
"errors"
"fmt"
apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/client-go/rest"
"k8s.io/klog"
"github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha1"
"github.com/jetstack/cert-manager/pkg/client/clientset/versioned/scheme"
)
type Webhook struct {
restConfigShallowCopy rest.Config
}
func (r *Webhook) Name() string {
return "webhook"
}
// Present creates a TXT record using the specified parameters
func (r *Webhook) Present(ch *v1alpha1.ChallengeRequest) error {
pl := &v1alpha1.ChallengePayload{
Request: ch,
}
pl.Request.Action = v1alpha1.ChallengeActionPresent
cfg, err := r.loadConfig(*ch.Config)
if err != nil {
return err
}
cl, err := r.restClientForGroup(cfg.GroupName)
if err != nil {
return err
}
result := cl.Post().Resource(cfg.SolverName).Body(&pl).Do()
// we will check this error after parsing the response
resErr := result.Error()
// TODO: handle metav1.Status response type and print better error messages
var respPayload v1alpha1.ChallengePayload
if err := result.Into(&respPayload); err != nil {
return utilerrors.NewAggregate([]error{resErr, err})
}
if respPayload.Response.Success && resErr == nil {
klog.Infof("Present call succeeded")
return nil
}
if respPayload.Response.Result == nil {
return utilerrors.NewAggregate([]error{
resErr,
fmt.Errorf("invalid payload response, did not succeed but no result provided"),
})
}
if respPayload.Response.Result.Message != "" {
return errors.New(respPayload.Response.Result.Message)
}
return resErr
}
// CleanUp removes the TXT record matching the specified parameters
func (r *Webhook) CleanUp(ch *v1alpha1.ChallengeRequest) error {
pl := &v1alpha1.ChallengePayload{
Request: ch,
}
pl.Request.Action = v1alpha1.ChallengeActionCleanUp
cfg, err := r.loadConfig(*ch.Config)
if err != nil {
return err
}
cl, err := r.restClientForGroup(cfg.GroupName)
if err != nil {
return err
}
result := cl.Post().Resource(cfg.SolverName).Body(&pl).Do()
// we will check this error after parsing the response
resErr := result.Error()
// TODO: handle metav1.Status response type and print better error messages
var respPayload v1alpha1.ChallengePayload
if err := result.Into(&respPayload); err != nil {
return utilerrors.NewAggregate([]error{resErr, err})
}
if respPayload.Response.Success && resErr == nil {
klog.Infof("CleanUp call succeeded")
return nil
}
if respPayload.Response.Result == nil {
return utilerrors.NewAggregate([]error{
resErr,
fmt.Errorf("invalid payload response, did not succeed but no result provided"),
})
}
if respPayload.Response.Result.Message != "" {
return errors.New(respPayload.Response.Result.Message)
}
return resErr
}
func (r *Webhook) Initialize(kubeClientConfig *rest.Config, stopCh <-chan struct{}) error {
cfgShallowCopy := *kubeClientConfig
cfgShallowCopy.APIPath = "/apis"
cfgShallowCopy.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs}
// We defer setting the GroupVersion of the rest client config to the
// restClientForGroup function.
if cfgShallowCopy.UserAgent == "" {
cfgShallowCopy.UserAgent = rest.DefaultKubernetesUserAgent()
}
r.restConfigShallowCopy = cfgShallowCopy
return nil
}
func (r *Webhook) loadConfig(cfgJSON apiext.JSON) (*cmapi.ACMEIssuerDNS01ProviderWebhook, error) {
cfg := cmapi.ACMEIssuerDNS01ProviderWebhook{}
if err := json.Unmarshal(cfgJSON.Raw, &cfg); err != nil {
return nil, fmt.Errorf("error decoding solver config: %v", err)
}
return &cfg, nil
}
func (r *Webhook) restClientForGroup(g string) (*rest.RESTClient, error) {
cfg := r.restConfigShallowCopy
cfg.GroupVersion = &schema.GroupVersion{
Group: g,
Version: v1alpha1.SchemeGroupVersion.Version,
}
return rest.RESTClientFor(&cfg)
}