mirror of
https://github.com/wahyd4/cert-manager.git
synced 2026-08-15 16:16:17 +10:00
Merge pull request #1563 from munnerz/webhook-dns-provider
Add webhook dns provider implementation
This commit is contained in:
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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
@@ -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)
|
||||
}
|
||||
Reference in New Issue
Block a user