-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsolver.go
172 lines (143 loc) · 6.54 KB
/
solver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package dns
import (
"fmt"
"github.com/camaoag/cert-manager-webhook-project-pinto/internal/gopinto"
"github.com/camaoag/cert-manager-webhook-project-pinto/internal/logutils"
log "github.com/sirupsen/logrus"
"strings"
"github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
)
// ProviderSolver is the struct implementing the webhook.Solver interface
// for pinto DNS
type ProviderSolver struct {
k8Client kubernetes.Interface
client *gopinto.APIClient
config *Config
apiKey string
provider string
environment string
}
func (p *ProviderSolver) Name() string {
return p.getConfig().Name()
}
// Present is responsible for actually presenting the DNS record with the
// DNS provider.
// This method should tolerate being called multiple times with the same value.
// cert-manager itself will later perform a self check to ensure that the
// solver has correctly configured the DNS provider.
func (p *ProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error {
log.WithFields(logutils.CreateChallengeFields(ch)).Info("Presenting new challenge ...")
configErr := p.getConfig().init(p.k8Client, ch)
if configErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(configErr).Error("Failed to retrieve configuration")
return configErr
}
apiClient, err := p.getDomainAPIClient()
if err != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(err).Error("Failed to retrieve API client")
return err
}
createModel, modelErr := p.getCreateRecordRequestModel(p.createRecordFromChallenge(ch), ch.ResolvedZone)
if modelErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(modelErr).Error("Failed to create request model for entry creation")
return modelErr
}
apiOptions, createOptionsErr := p.createAPIOptions(nil)
if createOptionsErr != nil {
return createOptionsErr
}
createRequestModel := apiClient.RecordApi.DnsApiRecordsPost(p.config.getContext()).
CreateRecordRequestModel(createModel).XApiOptions(apiOptions)
log.WithFields(logutils.CreateModelFields(ch, createRequestModel)).Trace("Prepared entry creation")
_, response, creationErr := createRequestModel.Execute()
if creationErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(creationErr).Error("API returned failure during challenge creation")
return creationErr
}
log.WithFields(logutils.CreateResponseFields(ch, response)).Trace("Successfully created challenge")
log.WithFields(logutils.CreateChallengeFields(ch)).Info("Presenting new challenge finished")
return nil
}
// CleanUp should delete the relevant TXT record from the DNS provider console.
// If multiple TXT records exist with the same record name (e.g.
// _acme-challenge.example.com) then **only** the record with the same `key`
// value provided on the ChallengeRequest should be cleaned up.
// This is in order to facilitate multiple DNS validations for the same domain
// concurrently.
func (p *ProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
log.WithFields(logutils.CreateChallengeFields(ch)).Info("Cleaning up ...")
apiClient, err := p.getDomainAPIClient()
if err != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(err).Error()
return err
}
//TODO BEGIN replace later when it is possible to delete by ID
records, retrieveErr := p.getEntriesToPreserve(ch)
if retrieveErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(retrieveErr).Error("Failed to retrieve existing DNS records")
return retrieveErr
}
log.WithFields(logutils.CreateChallengeFields(ch)).Trace("Retrieved list of TXT records to be readded")
//TODO END
// if multiple entries with the same name are defined, we have to force the deletion of all
apiOptions, createOptionsErr := p.createAPIOptions(map[string]string{
"force": "true",
})
if createOptionsErr != nil {
return createOptionsErr
}
deletionModel := apiClient.RecordApi.DnsApiRecordsDelete(p.getConfig().getContext()).
Zone(ch.ResolvedZone).
RecordType(gopinto.RECORDTYPE_TXT).
Name(strings.TrimSuffix(strings.TrimSuffix(ch.ResolvedFQDN, ch.ResolvedZone), ".")).
XApiOptions(apiOptions)
log.WithFields(logutils.CreateModelFields(ch, deletionModel)).Trace("Prepared deletion model")
deletionResponse, deletionErr := deletionModel.Execute()
if deletionErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(deletionErr).Error("API returned failure during entry deletion")
return deletionErr
}
log.WithFields(logutils.CreateResponseFields(ch, deletionResponse)).Trace("Successfully deleted entries")
//TODO BEGIN replace later when it is possible to delete by ID
// re add entries
for _, record := range records {
recordModel, modelErr := p.getCreateRecordRequestModel(record, ch.ResolvedZone)
if modelErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(modelErr).Error("Failed to create request model for recreation of entry")
return modelErr
}
creationModel := apiClient.RecordApi.DnsApiRecordsPost(p.getConfig().getContext()).
CreateRecordRequestModel(recordModel)
log.WithFields(logutils.CreateModelFields(ch, creationModel)).Trace("Prepared recreation of re-add job")
_, response, creationErr := creationModel.Execute()
if creationErr != nil {
log.WithFields(logutils.CreateChallengeFields(ch)).WithError(creationErr).Error("API returned failure during recreation of entries")
return creationErr
}
log.WithFields(logutils.CreateResponseFields(ch, response)).Trace("Successfully created re-add entry")
}
//TODO END
log.WithFields(logutils.CreateChallengeFields(ch)).Info("Cleaning up finished")
return nil
}
// Initialize will be called when the webhook first starts.
// This method can be used to instantiate the webhook, i.e. initialising
// connections or warming up caches.
// Typically, the kubeClientConfig parameter is used to build a Kubernetes
// k8Client that can be used to fetch resources from the Kubernetes API, e.g.
// Secret resources containing credentials used to authenticate with DNS
// provider accounts.
// The stopCh can be used to handle early termination of the webhook, in cases
// where a SIGTERM or similar signal is sent to the webhook process.
func (p *ProviderSolver) Initialize(kubeClientConfig *rest.Config, _ <-chan struct{}) error {
log.Info("Initialize kube client ...")
cl, err := kubernetes.NewForConfig(kubeClientConfig)
if err != nil {
return fmt.Errorf("failed to get kubernetes k8Client: %w", err)
}
p.k8Client = cl
log.Info("Initialization kube client is finished")
return nil
}