first commit
This commit is contained in:
commit
e8a57ace39
16 changed files with 1486 additions and 0 deletions
240
main.go
Normal file
240
main.go
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/acme/webhook/apis/acme/v1alpha1"
|
||||
"github.com/jetstack/cert-manager/pkg/acme/webhook/cmd"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
extapiv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
extapiv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
var GroupName = os.Getenv("GROUP_NAME")
|
||||
|
||||
func main() {
|
||||
if GroupName == "" {
|
||||
panic("GROUP_NAME must be specified")
|
||||
}
|
||||
cmd.RunWebhookServer(GroupName,
|
||||
&TemplateSolver{},
|
||||
)
|
||||
}
|
||||
|
||||
type TemplateSolver struct {
|
||||
sync.Mutex
|
||||
cl *kubernetes.Clientset
|
||||
cache map[string]struct{}
|
||||
}
|
||||
|
||||
type TemplateSolverConfig struct {
|
||||
// `issuer.spec.acme.dns01.providers.webhook.config` field.
|
||||
|
||||
APIKeySecretRef corev1.SecretKeySelector `json:"apiKeySecretRef"`
|
||||
URLTemplate string `json:"urlTemplate"`
|
||||
URLData *extapiv1.JSON `json:"urlData,omitempty"`
|
||||
Headers *extapiv1.JSON `json:"headers,omitempty"`
|
||||
BodyTemplate string `json:"bodyTemplate,omitempty"`
|
||||
BodyData *extapiv1.JSON `json:"bodyData,omitempty"`
|
||||
}
|
||||
|
||||
type TemplateConfig struct {
|
||||
SecretName string
|
||||
SecretKey string
|
||||
Headers map[string]string
|
||||
URLTemp *template.Template
|
||||
BodyTemp *template.Template
|
||||
Data TemplateData
|
||||
}
|
||||
|
||||
type TemplateData struct {
|
||||
DNSKey string // DNS Key
|
||||
APIKey string // API Key
|
||||
URLData interface{} // URLData
|
||||
BodyData interface{} // BodyData
|
||||
}
|
||||
|
||||
func (*TemplateSolver) Name() string {
|
||||
return "template-solver"
|
||||
}
|
||||
|
||||
func loadConfig(cfg *extapiv1beta1.JSON) (*TemplateConfig, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("error missing solver config")
|
||||
}
|
||||
var c TemplateSolverConfig
|
||||
err := json.Unmarshal(cfg.Raw, &c)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error decoding solver config: %v", err)
|
||||
}
|
||||
|
||||
if c.URLTemplate == "" {
|
||||
return nil, fmt.Errorf("error empty urlTemplate")
|
||||
}
|
||||
tc := &TemplateConfig{
|
||||
SecretName: c.APIKeySecretRef.Name,
|
||||
SecretKey: c.APIKeySecretRef.Key,
|
||||
Headers: make(map[string]string),
|
||||
Data: TemplateData{},
|
||||
}
|
||||
tc.URLTemp, err = template.New("url").Parse(c.URLTemplate)
|
||||
if err != nil {
|
||||
return tc, fmt.Errorf("error parsing urlTemplate: %v", err)
|
||||
}
|
||||
if c.URLData == nil {
|
||||
return tc, fmt.Errorf("error missing urlData field")
|
||||
}
|
||||
if err = json.Unmarshal(c.URLData.Raw, &tc.Data.URLData); err != nil {
|
||||
return tc, fmt.Errorf("error decoding urlData: %v", err)
|
||||
}
|
||||
|
||||
if c.Headers != nil {
|
||||
if err = json.Unmarshal(c.Headers.Raw, &tc.Headers); err != nil {
|
||||
return tc, fmt.Errorf("error decoding headers: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if c.BodyTemplate != "" {
|
||||
tc.BodyTemp, err = template.New("body").Parse(c.BodyTemplate)
|
||||
if err != nil {
|
||||
return tc, fmt.Errorf("error parsing bodyTemplate: %v", err)
|
||||
}
|
||||
if c.BodyData == nil {
|
||||
return tc, fmt.Errorf("error missing bodyData field")
|
||||
}
|
||||
if err = json.Unmarshal(c.BodyData.Raw, &tc.Data.BodyData); err != nil {
|
||||
return tc, fmt.Errorf("error decoding bodyData: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
return tc, nil
|
||||
}
|
||||
|
||||
func request(tc *TemplateConfig) error {
|
||||
var (
|
||||
body bytes.Buffer
|
||||
url bytes.Buffer
|
||||
err error
|
||||
)
|
||||
if tc.BodyTemp != nil {
|
||||
if err = tc.BodyTemp.Execute(&body, &tc.Data); err != nil {
|
||||
return fmt.Errorf("error executing bodyTemplate: %v", err)
|
||||
}
|
||||
}
|
||||
if err = tc.URLTemp.Execute(&url, &tc.Data); err != nil {
|
||||
return fmt.Errorf("error executing urlTemplate: %v", err)
|
||||
}
|
||||
|
||||
var req *http.Request
|
||||
if body.Len() == 0 {
|
||||
req, err = http.NewRequest("GET", url.String(), nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error constructing GET request: %v", err)
|
||||
}
|
||||
} else {
|
||||
req, err = http.NewRequest("POST", url.String(), &body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error constructing POST request: %v", err)
|
||||
}
|
||||
}
|
||||
for k, v := range tc.Headers {
|
||||
req.Header.Set(k, v)
|
||||
}
|
||||
c := &http.Client{Transport: &http.Transport{
|
||||
DialContext: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
}).DialContext,
|
||||
}}
|
||||
_, err = c.Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error response: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TemplateSolver) getSecret(tc *TemplateConfig, ns string) error {
|
||||
s, err := ts.cl.CoreV1().Secrets(ns).Get(tc.SecretName, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, ok := s.Data[tc.SecretKey]
|
||||
if !ok {
|
||||
return fmt.Errorf("no api key for %q in secret '%s/%s'", tc.SecretKey, ns, tc.SecretName)
|
||||
}
|
||||
tc.Data.APIKey = string(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ts *TemplateSolver) addKey(k string) bool {
|
||||
ts.Lock()
|
||||
_, ok := ts.cache[k]
|
||||
if ok {
|
||||
return ok
|
||||
}
|
||||
ts.cache[k] = struct{}{}
|
||||
ts.Unlock()
|
||||
return false
|
||||
}
|
||||
|
||||
func (ts *TemplateSolver) removeKey(k string) {
|
||||
ts.Lock()
|
||||
delete(ts.cache, k)
|
||||
ts.Unlock()
|
||||
}
|
||||
|
||||
func (ts *TemplateSolver) Present(ch *v1alpha1.ChallengeRequest) error {
|
||||
if ts.addKey(ch.Key) {
|
||||
return nil
|
||||
}
|
||||
|
||||
tc, err := loadConfig(ch.Config)
|
||||
if err != nil {
|
||||
ts.removeKey(ch.Key)
|
||||
return err
|
||||
}
|
||||
tc.Data.DNSKey = ch.Key
|
||||
err = ts.getSecret(tc, ch.ResourceNamespace)
|
||||
if err != nil {
|
||||
ts.removeKey(ch.Key)
|
||||
return err
|
||||
}
|
||||
err = request(tc)
|
||||
if err != nil {
|
||||
ts.removeKey(ch.Key)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (ts *TemplateSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error {
|
||||
ts.removeKey(ch.Key)
|
||||
tc, err := loadConfig(ch.Config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = ts.getSecret(tc, ch.ResourceNamespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return request(tc)
|
||||
}
|
||||
|
||||
func (ts *TemplateSolver) Initialize(cfg *rest.Config, stopCh <-chan struct{}) error {
|
||||
cl, err := kubernetes.NewForConfig(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ts.cl = cl
|
||||
ts.cache = make(map[string]struct{})
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue