From 6f180147ace32ec37ef73b386ef33fb7e2726d04 Mon Sep 17 00:00:00 2001 From: "Jens-U. Mozdzen" Date: Sun, 3 Jul 2022 23:48:49 +0200 Subject: [PATCH] updated Dockerfile to use latest Alpine image, optimized logging and updated documentation --- Dockerfile | 4 +-- Makefile | 7 +++-- README.md | 35 +++++++++++++----------- go.mod | 2 +- main.go | 18 ++++++------- variomediaclient_api2019.go | 54 ++++++++++++++++++------------------- 6 files changed, 64 insertions(+), 56 deletions(-) diff --git a/Dockerfile b/Dockerfile index 9b49cee..e8854de 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.16-alpine AS build_deps +FROM golang:1.17-alpine AS build_deps RUN apk add --no-cache git @@ -15,7 +15,7 @@ COPY . . RUN CGO_ENABLED=0 go build -o webhook -ldflags '-w -extldflags "-static"' . -FROM alpine:3.9 +FROM alpine RUN apk add --no-cache ca-certificates diff --git a/Makefile b/Makefile index 246b737..9059f55 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ ARCH ?= $(shell go env GOARCH) PROVIDER := "variomedia" IMAGE_NAME := "${REGISTRY}cert-manager-webhook-${PROVIDER}" -IMAGE_TAG := "latest" +IMAGE_TAG := "2.0.0" OUT := $(shell pwd)/_out @@ -30,9 +30,12 @@ clean: clean-kubebuilder clean-kubebuilder: rm -Rf _test/kubebuilder -build: +build: test docker build -t "$(IMAGE_NAME):$(IMAGE_TAG)" . +push: build + docker push $(IMAGE_NAME):$(IMAGE_TAG) + .PHONY: rendered-manifest.yaml rendered-manifest.yaml: helm template \ diff --git a/README.md b/README.md index 0754b85..98b271f 100644 --- a/README.md +++ b/README.md @@ -49,25 +49,15 @@ webhook to complete ACME challenge validations and obtain certificates. The Variomedia AG webhook implementation is based on the example webhook provided by the cert-manager project (https://github.com/cert-manager/webhook-example). -### Creating your own repository +### Using your own repository The GitHub version of the Variomedia webhook implementation is focussed on providing an implementation in a decentral container registry, i.e. "Harbor". The Docker image is currently *not* published on docker.io. -Once you have your registry up & running (which is not part of this README description), -you can build your local copy of the software using the following commands: - -```bash -# to upload the container image to your registry -export REGISTRY='your.registry.company.com' -docker login $REGISTRY -make build -``` - #### Running the test suite -**It is essential that you configure and run the test suite after creating the +**It is essential that you configure and run the test suite after modifying the DNS01 webhook.** You can run the test suite with: @@ -76,8 +66,23 @@ You can run the test suite with: $ TEST_ZONE_NAME=example.com. make test ``` -The example file has a number of areas you must fill in and replace with your -own options in order for tests to pass. +Setting the trailing "." on the zone name (for which you have the Variomedia API key +and set up the files in the testdata/my-custom-solver/ subdirectory) is required, the +test run might otherwise fail. + +### Pushing the Docker image +Once you have your registry up & running (which is not part of this README description), +you can build and upload your local copy of the software using the following commands: + +```bash +# to upload the container image to your registry +export REGISTRY='your.registry.company.com/yourproject' +docker login $REGISTRY + +# push the resulting image to your repository +# will invoke via dependencies test -> build -> push +TEST_ZONE_NAME=example.com. make push +``` ## Installation via Helm chart @@ -119,7 +124,7 @@ kubectl apply -f - << EOF - dns01: webhook: groupName: acme.cert-manager-webhook-variomedia.local - solverName: variomedia + solverName: variomedia-APIv2019 config: example.com: variomedia-credentials-01 someotherdomain.com: variomedia-credentials-01 diff --git a/go.mod b/go.mod index 8ad2f46..4db30fb 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module github.com/cert-manager/webhook-example +module github.com/jmozd/cert-manager-webhook-variomedia go 1.17 diff --git a/main.go b/main.go index 34dd461..1d3e200 100644 --- a/main.go +++ b/main.go @@ -124,14 +124,14 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { cfg, err := c.loadApiKeys(ch.Config, ch.ResourceNamespace) if err != nil { - klog.V(2).ErrorS( err, "Present() finished with error while loading API keys") + klog.ErrorS( err, "Present() finished with error while loading API keys") return err } klog.V(6).Infof("decoded configuration %v", cfg) entry, domain, apiKey, err := c.getDomainAndEntryAndApiKey( ch, &cfg) if err != nil { - klog.V(2).ErrorS( err, "Present() finished with error while determining domain and entry name") + klog.ErrorS( err, "Present() finished with error while determining domain and entry name") return fmt.Errorf("unable to get domain key for zone %s: %v", ch.ResolvedZone, err) } klog.V(4).InfoS( "present", "entry", entry, "domain", domain, "entry", entry, "API key", apiKey) @@ -140,7 +140,7 @@ func (c *customDNSProviderSolver) Present(ch *v1alpha1.ChallengeRequest) error { url, err := variomediaClient.UpdateTxtRecord(&domain, &entry, &ch.Key, variomediaMinTtl) if err != nil { - klog.V(2).ErrorS( err, "Present() finished with error while trying to update the DNS record") + klog.ErrorS( err, "Present() finished with error while trying to update the DNS record") return fmt.Errorf("unable to change TXT record: %v", err) } @@ -170,14 +170,14 @@ func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { cfg, err := c.loadApiKeys(ch.Config, ch.ResourceNamespace) if err != nil { - klog.V(2).ErrorS( err, "CleanUp() finished with error while loading API keys") + klog.ErrorS( err, "CleanUp() finished with error while loading API keys") return err } klog.V(6).Infof("decoded configuration %v", cfg) entry, domain, apiKey, err := c.getDomainAndEntryAndApiKey( ch, &cfg) if err != nil { - klog.V(2).ErrorS( err, "CleanUp() finished with error while determining domain and entry name") + klog.ErrorS( err, "CleanUp() finished with error while determining domain and entry name") return fmt.Errorf("unable to get domain key for zone %s: %v", ch.ResolvedZone, err) } klog.V(4).InfoS( "clean up", "entry", entry, "domain", domain, "entry", entry, "API key", apiKey) @@ -188,7 +188,7 @@ func (c *customDNSProviderSolver) CleanUp(ch *v1alpha1.ChallengeRequest) error { err = variomediaClient.DeleteTxtRecord( url, variomediaMinTtl) if err != nil { - klog.V(2).ErrorS( err, "CleanUp() finished with error while trying to delete the DNS record") + klog.ErrorS( err, "CleanUp() finished with error while trying to delete the DNS record") return fmt.Errorf("unable to delete TXT record: %v", err) } @@ -238,13 +238,13 @@ func (c *customDNSProviderSolver) loadApiKeys(cfgJSON *extapi.JSON, namespace st klog.V(6).Infof("try to load secret `%s` with key `%s`", secretName, "api-token") sec, err := c.client.CoreV1().Secrets(namespace).Get(context.Background(), secretName, metav1.GetOptions{}) if err != nil { - klog.V(2).ErrorS( err, "loadApiKeys() finished with error") + klog.ErrorS( err, "loadApiKeys() finished with error") return nil, fmt.Errorf("unable to get secret `%s`; %v", secretName, err) } secBytes, ok := sec.Data["api-token"] if !ok { - klog.V(2).ErrorS( err, "loadApiKeys() finished with error") + klog.ErrorS( err, "loadApiKeys() finished with error") return nil, fmt.Errorf("key %q not found in secret \"%s/%s\"", "api-token", secretName, namespace) } @@ -268,7 +268,7 @@ func (c *customDNSProviderSolver) getDomainAndEntryAndApiKey(ch *v1alpha1.Challe domain := strings.TrimSuffix(ch.ResolvedZone, ".") apiKey, ok := (*cfg)[domain] if !ok { - klog.V(2).ErrorS( fmt.Errorf("domain '%s' not found in config.", domain), "getDomainAndEntryAndApiKey() finished with error") + klog.ErrorS( fmt.Errorf("domain '%s' not found in config.", domain), "getDomainAndEntryAndApiKey() finished with error") return entry, domain, apiKey, fmt.Errorf("domain '%s' not found in config.", domain) } diff --git a/variomediaclient_api2019.go b/variomediaclient_api2019.go index 46e86de..4d21887 100644 --- a/variomediaclient_api2019.go +++ b/variomediaclient_api2019.go @@ -124,32 +124,32 @@ func (c *variomediaClient) UpdateTxtRecord(domain *string, name *string, value * // the actual request is encoded in JSON body, err := json.Marshal( reqData) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", fmt.Errorf("cannot marshall to json: %v", err) } req, err := http.NewRequest("POST", variomediaLiveDnsBaseUrl, bytes.NewReader(body)) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", err } // contact Variomedia and check the results status, respData, err := c.doRequest(req, true) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", err } // have we hit the rate limit? if status == http.StatusTooManyRequests { - klog.V(2).ErrorS( fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests), "UpdateTxtRecord() finished with error") + klog.ErrorS( nil, "UpdateTxtRecord() finished with errori 'too many requests' reported by Variomedia") return "", fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests) } if status != http.StatusCreated && status != http.StatusOK && status != http.StatusAccepted { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") - return "", fmt.Errorf("failed creating TXT record: %v", err) + klog.ErrorS(nil, "UpdateTxtRecord() finished with error reported by server", "status code", status) + return "", fmt.Errorf("failed creating TXT record: server reported status code %d", status) } // the request has succeeded - but is the job already finished? @@ -157,7 +157,7 @@ func (c *variomediaClient) UpdateTxtRecord(domain *string, name *string, value * var reply variomediaResponse err = json.Unmarshal( respData, &reply) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", fmt.Errorf("cannot unmarshall response to json: %v", err) } klog.V(5).InfoS( "HTTP finished", "JSON reply", reply) @@ -174,33 +174,33 @@ func (c *variomediaClient) UpdateTxtRecord(domain *string, name *string, value * // re-fetch the job status req, err := http.NewRequest("GET", reply.Data.Links[ "queue-job"], nil) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", err } // contact Variomedia and check the results status, respData, err := c.doRequest(req, true) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", err } // have we hit the rate limit? if status == http.StatusTooManyRequests { - klog.V(2).ErrorS(fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests), "UpdateTxtRecord() finished with error") + klog.ErrorS( nil, "UpdateTxtRecord() finished with errori 'too many requests' reported by Variomedia") return "", fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests) } if status != http.StatusCreated && status != http.StatusOK && status != http.StatusAccepted { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") - return "", fmt.Errorf("failed creating TXT record: %v", err) + klog.ErrorS(nil, "UpdateTxtRecord() finished with error reported by server", "status code", status) + return "", fmt.Errorf("failed creating TXT record: server reported status code %d", status) } // the request has succeeded - but is the job already finished? // check the response for an according '' element err = json.Unmarshal( respData, &reply) if err != nil { - klog.V(2).ErrorS(err, "UpdateTxtRecord() finished with error") + klog.ErrorS(err, "UpdateTxtRecord() finished with error") return "", fmt.Errorf("cannot unmarshall response to json: %v", err) } klog.V(5).InfoS( "HTTP finished", "JSON reply", reply) @@ -213,7 +213,7 @@ func (c *variomediaClient) UpdateTxtRecord(domain *string, name *string, value * break; } if (loopcount == 0) { - klog.V(2).ErrorS(fmt.Errorf("DNS update job timed out with most recent status '%s'", reply.Data.Attributes[ "status"]), "UpdateTxtRecord() finished with error") + klog.ErrorS(nil, "UpdateTxtRecord() finished with error: job timed out", "most recent status", reply.Data.Attributes[ "status"]) return "", fmt.Errorf("DNS update job timed out with most recent status '%s'", reply.Data.Attributes[ "status"]) } } // emulated do until @@ -237,25 +237,25 @@ func (c *variomediaClient) DeleteTxtRecord(url string, ttl int) error { // deleting a record happens by sending a HTTP "DELETE" request to the DNS entry's URL req, err := http.NewRequest("DELETE", url, nil) if err != nil { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(err, "DeleteTxtRecord() finished with error") return err } // contact Variomedia and check the results status, respData, err := c.doRequest(req, true) if err != nil { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(err, "DeleteTxtRecord() finished with error") return err } // have we hit the rate limit? if status == http.StatusTooManyRequests { - klog.V(2).ErrorS(fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests), "DeleteTxtRecord() finished with error") + klog.ErrorS( nil, "DeleteTxtRecord() finished with errori 'too many requests' reported by Variomedia") return fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests) } if status != http.StatusCreated && status != http.StatusOK && status != http.StatusAccepted { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(nil, "DeleteTxtRecord() finished with error reported by server", "status code", status) return fmt.Errorf("failed deleting TXT record: %v", err) } @@ -264,7 +264,7 @@ func (c *variomediaClient) DeleteTxtRecord(url string, ttl int) error { var reply variomediaResponse err = json.Unmarshal( respData, &reply) if err != nil { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(err, "DeleteTxtRecord() finished with error") return fmt.Errorf("cannot unmarshall response to json: %v", err) } klog.V(5).InfoS( "HTTP finished", "JSON reply", reply) @@ -281,25 +281,25 @@ func (c *variomediaClient) DeleteTxtRecord(url string, ttl int) error { // re-fetch the job status req, err := http.NewRequest("GET", reply.Data.Links[ "queue-job"], nil) if err != nil { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(err, "DeleteTxtRecord() finished with error") return err } // contact Variomedia and check the results status, respData, err := c.doRequest(req, true) if err != nil { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(err, "DeleteTxtRecord() finished with error") return err } // have we hit the rate limit? if status == http.StatusTooManyRequests { - klog.V(2).ErrorS(fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests), "DeleteTxtRecord() finished with error") + klog.ErrorS( nil, "DeleteTxtRecord() finished with errori 'too many requests' reported by Variomedia") return fmt.Errorf("Variomedia rate limit reached (HTTP code %d)", http.StatusTooManyRequests) } if status != http.StatusCreated && status != http.StatusOK && status != http.StatusAccepted && status != http.StatusNotFound { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(nil, "DeleteTxtRecord() finished with error reported by server", "status code", status) return fmt.Errorf("failed creating TXT record: %v", err) } @@ -307,7 +307,7 @@ func (c *variomediaClient) DeleteTxtRecord(url string, ttl int) error { // check the response for an according '' element err = json.Unmarshal( respData, &reply) if err != nil { - klog.V(2).ErrorS(err, "DeleteTxtRecord() finished with error") + klog.ErrorS(err, "DeleteTxtRecord() finished with error") return fmt.Errorf("cannot unmarshall response to json: %v", err) } klog.V(5).InfoS( "HTTP finished", "JSON reply", reply) @@ -327,7 +327,7 @@ func (c *variomediaClient) DeleteTxtRecord(url string, ttl int) error { break; } if (loopcount == 0) { - klog.V(2).ErrorS(fmt.Errorf("DNS update job timed out with most recent status '%s'", reply.Data.Attributes[ "status"]), "DeleteTxtRecord() finished with error") + klog.ErrorS(nil, "DeleteTxtRecord() finished with error: job timed out", "most recent status", reply.Data.Attributes[ "status"]) return fmt.Errorf("DNS update job timed out with most recent status '%s'", reply.Data.Attributes[ "status"]) } } // emulated do until @@ -363,7 +363,7 @@ func (c *variomediaClient) doRequest(req *http.Request, readResponseBody bool) ( res, err := client.Do(req) if err != nil { - klog.V(2).ErrorS(err, "doRequest() finished with error") + klog.ErrorS(err, "doRequest() finished with error") return 0, nil, err } @@ -373,7 +373,7 @@ func (c *variomediaClient) doRequest(req *http.Request, readResponseBody bool) ( if (res.StatusCode == http.StatusOK || res.StatusCode == http.StatusAccepted) && readResponseBody { data, err := ioutil.ReadAll(res.Body) if err != nil { - klog.V(2).ErrorS(err, "HTTP request finished with error") + klog.ErrorS(err, "HTTP request finished with error") return 0, nil, err } klog.V(4).InfoS( "HTTP request succeeded", "status code", res.StatusCode)