diff --git a/backend/embed/acme.sh b/backend/embed/acme.sh index d62f7007..8ded1465 100755 --- a/backend/embed/acme.sh +++ b/backend/embed/acme.sh @@ -1768,7 +1768,7 @@ _inithttp() { if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then _ACME_CURL="$_ACME_CURL -L " fi - if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then + if [ "$DEBUG" ] && [ "$DEBUG" -ge 2 ]; then _CURL_DUMP="$(_mktemp)" _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP " fi @@ -1808,6 +1808,8 @@ _inithttp() { } +_HTTP_MAX_RETRY=8 + # body url [needbase64] [POST|PUT|DELETE] [ContentType] _post() { body="$1" @@ -1815,6 +1817,33 @@ _post() { needbase64="$3" httpmethod="$4" _postContentType="$5" + _sleep_retry_sec=1 + _http_retry_times=0 + _hcode=0 + while [ "${_http_retry_times}" -le "$_HTTP_MAX_RETRY" ]; do + [ "$_http_retry_times" = "$_HTTP_MAX_RETRY" ] + _lastHCode="$?" + _debug "Retrying post" + _post_impl "$body" "$_post_url" "$needbase64" "$httpmethod" "$_postContentType" "$_lastHCode" + _hcode="$?" + _debug _hcode "$_hcode" + if [ "$_hcode" = "0" ]; then + break + fi + _http_retry_times=$(_math $_http_retry_times + 1) + _sleep $_sleep_retry_sec + done + return $_hcode +} + +# body url [needbase64] [POST|PUT|DELETE] [ContentType] [displayError] +_post_impl() { + body="$1" + _post_url="$2" + needbase64="$3" + httpmethod="$4" + _postContentType="$5" + displayError="$6" if [ -z "$httpmethod" ]; then httpmethod="POST" @@ -1866,7 +1895,9 @@ _post() { fi _ret="$?" if [ "$_ret" != "0" ]; then - _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" + if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then + _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" + fi if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _err "Here is the curl dump log:" _err "$(cat "$_CURL_DUMP")" @@ -1922,7 +1953,9 @@ _post() { _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." fi if [ "$_ret" != "0" ]; then - _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" + if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then + _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret" + fi fi _sed_i "s/^ *//g" "$HTTP_HEADER" else @@ -1936,13 +1969,38 @@ _post() { # url getheader timeout _get() { + url="$1" + onlyheader="$2" + t="$3" + _sleep_retry_sec=1 + _http_retry_times=0 + _hcode=0 + while [ "${_http_retry_times}" -le "$_HTTP_MAX_RETRY" ]; do + [ "$_http_retry_times" = "$_HTTP_MAX_RETRY" ] + _lastHCode="$?" + _debug "Retrying GET" + _get_impl "$url" "$onlyheader" "$t" "$_lastHCode" + _hcode="$?" + _debug _hcode "$_hcode" + if [ "$_hcode" = "0" ]; then + break + fi + _http_retry_times=$(_math $_http_retry_times + 1) + _sleep $_sleep_retry_sec + done + return $_hcode +} + +# url getheader timeout displayError +_get_impl() { _debug GET url="$1" onlyheader="$2" t="$3" + displayError="$4" _debug url "$url" _debug "timeout=$t" - + _debug "displayError" "$displayError" _inithttp if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then @@ -1961,7 +2019,9 @@ _get() { fi ret=$? if [ "$ret" != "0" ]; then - _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret" + if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then + _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret" + fi if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then _err "Here is the curl dump log:" _err "$(cat "$_CURL_DUMP")" @@ -1987,7 +2047,9 @@ _get() { _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." fi if [ "$ret" != "0" ]; then - _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret" + if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then + _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret" + fi fi else ret=$? @@ -3925,7 +3987,7 @@ _ns_lookup_ali() { } _ns_is_available_dp() { - if _get "https://dns.alidns.com" "" 1 >/dev/null 2>&1; then + if _get "https://doh.pub" "" 1 >/dev/null 2>&1; then return 0 else return 1 @@ -4145,6 +4207,10 @@ issue() { if [ -z "$_ACME_IS_RENEW" ]; then _initpath "$_main_domain" "$_key_length" mkdir -p "$DOMAIN_PATH" + else + Le_OrderFinalize="" + Le_LinkOrder="" + Le_LinkCert="" fi if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then @@ -4712,26 +4778,13 @@ $_authorizations_map" return 1 fi - _debug "sleep 2 secs to verify" - sleep 2 - _debug "checking" - - _send_signed_request "$uri" - - if [ "$?" != "0" ]; then - _err "$d:Verify error:$response" - _clearupwebbroot "$_currentRoot" "$removelevel" "$token" - _clearup - _on_issue_err "$_post_hook" "$vlist" - return 1 - fi _debug2 original "$response" response="$(echo "$response" | _normalizeJson)" _debug2 response "$response" status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') - + _debug2 status "$status" if _contains "$status" "invalid"; then error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')" _debug2 error "$error" @@ -4763,9 +4816,9 @@ $_authorizations_map" fi if [ "$status" = "pending" ]; then - _info "Pending" + _info "Pending, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" elif [ "$status" = "processing" ]; then - _info "Processing" + _info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)" else _err "$d:Verify error:$response" _clearupwebbroot "$_currentRoot" "$removelevel" "$token" @@ -4773,7 +4826,19 @@ $_authorizations_map" _on_issue_err "$_post_hook" "$vlist" return 1 fi + _debug "sleep 2 secs to verify again" + sleep 2 + _debug "checking" + _send_signed_request "$uri" + + if [ "$?" != "0" ]; then + _err "$d:Verify error:$response" + _clearupwebbroot "$_currentRoot" "$removelevel" "$token" + _clearup + _on_issue_err "$_post_hook" "$vlist" + return 1 + fi done done diff --git a/backend/embed/api_docs/components/DNSProviderObject.json b/backend/embed/api_docs/components/DNSProviderObject.json index ddad518c..1ebe10c7 100644 --- a/backend/embed/api_docs/components/DNSProviderObject.json +++ b/backend/embed/api_docs/components/DNSProviderObject.json @@ -7,8 +7,8 @@ "created_on", "modified_on", "user_id", - "provider_key", "name", + "acme_sh_name", "meta" ], "properties": { @@ -28,16 +28,16 @@ "type": "integer", "minimum": 1 }, - "provider_key": { - "type": "string", - "minLength": 1, - "maxLength": 100 - }, "name": { "type": "string", "minLength": 1, "maxLength": 100 }, + "acme_sh_name": { + "type": "string", + "minLength": 4, + "maxLength": 50 + }, "meta": { "type": "object" } diff --git a/backend/embed/api_docs/paths/dns-providers/get.json b/backend/embed/api_docs/paths/dns-providers/get.json index 19a298bf..5c99baef 100644 --- a/backend/embed/api_docs/paths/dns-providers/get.json +++ b/backend/embed/api_docs/paths/dns-providers/get.json @@ -67,12 +67,11 @@ "created_on": 1602593653, "modified_on": 1602593653, "user_id": 1, - "provider_key": "route53", "name": "Route53", + "acme_sh_name": "dns_aws", "meta": { - "access_key": "abc123", - "access_secret": "def098", - "zone_id": "ABC123" + "AWS_ACCESS_KEY_ID": "abc123", + "AWS_SECRET_ACCESS_KEY": "def098" } } ] diff --git a/backend/embed/api_docs/paths/dns-providers/post.json b/backend/embed/api_docs/paths/dns-providers/post.json index 35bc2bc3..8917d96c 100644 --- a/backend/embed/api_docs/paths/dns-providers/post.json +++ b/backend/embed/api_docs/paths/dns-providers/post.json @@ -36,12 +36,11 @@ "created_on": 1602593653, "modified_on": 1602593653, "user_id": 1, - "provider_key": "route53", "name": "Route53", + "acme_sh_name": "dns_aws", "meta": { - "access_key": "abc123", - "access_secret": "def098", - "zone_id": "ABC123" + "AWS_ACCESS_KEY_ID": "abc123", + "AWS_SECRET_ACCESS_KEY": "def098" } } } diff --git a/backend/embed/api_docs/paths/dns-providers/providerID/get.json b/backend/embed/api_docs/paths/dns-providers/providerID/get.json index 316e8d2e..27d7410b 100644 --- a/backend/embed/api_docs/paths/dns-providers/providerID/get.json +++ b/backend/embed/api_docs/paths/dns-providers/providerID/get.json @@ -40,12 +40,11 @@ "created_on": 1602593653, "modified_on": 1602593653, "user_id": 1, - "provider_key": "route53", "name": "Route53", + "acme_sh_name": "dns_aws", "meta": { - "access_key": "abc123", - "access_secret": "def098", - "zone_id": "ABC123" + "AWS_ACCESS_KEY_ID": "abc123", + "AWS_SECRET_ACCESS_KEY": "def098" } } } diff --git a/backend/embed/api_docs/paths/dns-providers/providerID/put.json b/backend/embed/api_docs/paths/dns-providers/providerID/put.json index 9b1d69fa..69b51339 100644 --- a/backend/embed/api_docs/paths/dns-providers/providerID/put.json +++ b/backend/embed/api_docs/paths/dns-providers/providerID/put.json @@ -50,12 +50,11 @@ "created_on": 1602593653, "modified_on": 1602593653, "user_id": 1, - "provider_key": "route53", "name": "Route53", + "acme_sh_name": "dns_aws", "meta": { - "access_key": "abc123", - "access_secret": "def098", - "zone_id": "ABC123" + "AWS_ACCESS_KEY_ID": "abc123", + "AWS_SECRET_ACCESS_KEY": "def098" } } } diff --git a/backend/embed/migrations/20201013035318_initial_schema.sql b/backend/embed/migrations/20201013035318_initial_schema.sql index 73535cfd..4d1b1af3 100644 --- a/backend/embed/migrations/20201013035318_initial_schema.sql +++ b/backend/embed/migrations/20201013035318_initial_schema.sql @@ -70,8 +70,8 @@ CREATE TABLE IF NOT EXISTS `dns_provider` created_on INTEGER NOT NULL DEFAULT 0, modified_on INTEGER NOT NULL DEFAULT 0, user_id INTEGER NOT NULL, - provider_key TEXT NOT NULL, name TEXT NOT NULL, + acme_sh_name TEXT NOT NULL, meta TEXT NOT NULL, is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id) @@ -92,6 +92,7 @@ CREATE TABLE IF NOT EXISTS `certificate` status TEXT NOT NULL, -- ready,requesting,failed,provided error_message text NOT NULL DEFAULT "", meta TEXT NOT NULL, + is_ecc INTEGER NOT NULL DEFAULT 0, is_deleted INTEGER NOT NULL DEFAULT 0, FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (certificate_authority_id) REFERENCES certificate_authority (id), diff --git a/backend/internal/acme/acmesh.go b/backend/internal/acme/acmesh.go index ddf3b7e8..9c83e072 100644 --- a/backend/internal/acme/acmesh.go +++ b/backend/internal/acme/acmesh.go @@ -1,5 +1,8 @@ package acme +// Some light reading: +// https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert + import ( "fmt" "io/ioutil" @@ -10,6 +13,7 @@ import ( "npm/embed" "npm/internal/config" + "npm/internal/entity/dnsprovider" "npm/internal/logger" ) @@ -17,7 +21,7 @@ var acmeShFile string // GetAcmeShVersion will return the acme.sh script version func GetAcmeShVersion() string { - if r, err := shExec("--version"); err == nil { + if r, err := shExec([]string{"--version"}, nil); err == nil { // modify the output r = strings.Trim(r, "\n") v := strings.Split(r, "\n") @@ -27,7 +31,7 @@ func GetAcmeShVersion() string { } // shExec executes the acme.sh with arguments -func shExec(args ...string) (string, error) { +func shExec(args []string, envs []string) (string, error) { if _, err := os.Stat(acmeShFile); os.IsNotExist(err) { e := fmt.Errorf("%s does not exist", acmeShFile) logger.Error("AcmeShError", e) @@ -37,6 +41,8 @@ func shExec(args ...string) (string, error) { logger.Debug("CMD: %s %v", acmeShFile, args) // nolint: gosec c := exec.Command(acmeShFile, args...) + c.Env = envs + b, e := c.Output() if e != nil { @@ -65,26 +71,22 @@ func WriteAcmeSh() { } // RequestCert does all the heavy lifting -func RequestCert(domains []string, method string) error { - args := []string{"--issue"} - - webroot := "/home/wwwroot/example.com" - - // Add domains to args - for _, domain := range domains { - args = append(args, "-d", domain) +func RequestCert(domains []string, method, caBundle, outputFullchainFile, outputKeyFile string, dnsProvider *dnsprovider.Model) error { + // TODO log file location configurable + args, err := buildCertRequestArgs(domains, method, caBundle, outputFullchainFile, outputKeyFile, dnsProvider) + if err != nil { + return err } - switch method { - // case "dns": - case "http": - args = append(args, "-w", webroot) - - default: - return fmt.Errorf("RequestCert method not supported: %s", method) + envs := make([]string, 0) + if dnsProvider != nil { + envs, err = dnsProvider.GetAcmeShEnvVars() + if err != nil { + return err + } } - ret, err := shExec(args...) + ret, err := shExec(args, envs) if err != nil { return err } @@ -93,3 +95,56 @@ func RequestCert(domains []string, method string) error { return nil } + +// This is split out into it's own function so it's testable +func buildCertRequestArgs(domains []string, method, caBundle, outputFullchainFile, outputKeyFile string, dnsProvider *dnsprovider.Model) ([]string, error) { + // TODO log file location configurable + args := []string{"--issue", "--log", "/data/logs/acme.sh.log"} + + if caBundle != "" { + args = append(args, "--ca-bundle", caBundle) + } + + if outputFullchainFile != "" { + args = append(args, "--fullchain-file", outputFullchainFile) + } + + if outputKeyFile != "" { + args = append(args, "--key-file", outputKeyFile) + } + + // TODO webroot location configurable + webroot := "/data/acme/wellknown" + + methodArgs := make([]string, 0) + switch method { + case "dns": + if dnsProvider == nil { + return nil, ErrDNSNeedsDNSProvider + } + methodArgs = append(methodArgs, "--dns", dnsProvider.AcmeShName) + + case "http": + if dnsProvider != nil { + return nil, ErrHTTPHasDNSProvider + } + methodArgs = append(methodArgs, "-w", webroot) + default: + return nil, ErrMethodNotSupported + } + + hasMethod := false + + // Add domains to args + for _, domain := range domains { + args = append(args, "-d", domain) + // Method has to appear after first domain, but does not need to be repeated + // for other domains. + if !hasMethod { + args = append(args, methodArgs...) + hasMethod = true + } + } + + return args, nil +} diff --git a/backend/internal/acme/acmesh_test.go b/backend/internal/acme/acmesh_test.go new file mode 100644 index 00000000..d48f57f8 --- /dev/null +++ b/backend/internal/acme/acmesh_test.go @@ -0,0 +1,190 @@ +package acme + +import ( + "testing" + + "npm/internal/entity/dnsprovider" + + "github.com/stretchr/testify/assert" +) + +// Tear up/down +/* +func TestMain(m *testing.M) { + config.Init(&version, &commit, &sentryDSN) + code := m.Run() + os.Exit(code) +} +*/ + +// TODO configurable +const acmeLogFile = "/data/logs/acme.sh.log" +const acmeWebroot = "/data/acme/wellknown" + +func TestBuildCertRequestArgs(t *testing.T) { + type want struct { + args []string + err error + } + + tests := []struct { + name string + domains []string + method string + caBundle string + outputFullchainFile string + outputKeyFile string + dnsProvider *dnsprovider.Model + want want + }{ + { + name: "http single domain", + domains: []string{"example.com"}, + method: "http", + caBundle: "", + outputFullchainFile: "/data/acme/certs/a.crt", + outputKeyFile: "/data/acme/certs/example.com.key", + dnsProvider: nil, + want: want{ + args: []string{ + "--issue", + "--log", + acmeLogFile, + "--fullchain-file", + "/data/acme/certs/a.crt", + "--key-file", + "/data/acme/certs/example.com.key", + "-d", + "example.com", + "-w", + acmeWebroot, + }, + err: nil, + }, + }, + { + name: "http multiple domains", + domains: []string{"example.com", "example-two.com", "example-three.com"}, + method: "http", + caBundle: "", + outputFullchainFile: "/data/acme/certs/a.crt", + outputKeyFile: "/data/acme/certs/example.com.key", + dnsProvider: nil, + want: want{ + args: []string{ + "--issue", + "--log", + acmeLogFile, + "--fullchain-file", + "/data/acme/certs/a.crt", + "--key-file", + "/data/acme/certs/example.com.key", + "-d", + "example.com", + "-w", + acmeWebroot, + "-d", + "example-two.com", + "-d", + "example-three.com", + }, + err: nil, + }, + }, + { + name: "http single domain with dns provider", + domains: []string{"example.com"}, + method: "http", + caBundle: "", + outputFullchainFile: "/data/acme/certs/a.crt", + outputKeyFile: "/data/acme/certs/example.com.key", + dnsProvider: &dnsprovider.Model{ + AcmeShName: "dns_cf", + }, + want: want{ + args: nil, + err: ErrHTTPHasDNSProvider, + }, + }, + { + name: "dns single domain", + domains: []string{"example.com"}, + method: "dns", + caBundle: "", + outputFullchainFile: "/data/acme/certs/a.crt", + outputKeyFile: "/data/acme/certs/example.com.key", + dnsProvider: &dnsprovider.Model{ + AcmeShName: "dns_cf", + }, + want: want{ + args: []string{ + "--issue", + "--log", + acmeLogFile, + "--fullchain-file", + "/data/acme/certs/a.crt", + "--key-file", + "/data/acme/certs/example.com.key", + "-d", + "example.com", + "--dns", + "dns_cf", + }, + err: nil, + }, + }, + { + name: "dns multiple domains", + domains: []string{"example.com", "example-two.com", "example-three.com"}, + method: "dns", + caBundle: "", + outputFullchainFile: "/data/acme/certs/a.crt", + outputKeyFile: "/data/acme/certs/example.com.key", + dnsProvider: &dnsprovider.Model{ + AcmeShName: "dns_cf", + }, + want: want{ + args: []string{ + "--issue", + "--log", + acmeLogFile, + "--fullchain-file", + "/data/acme/certs/a.crt", + "--key-file", + "/data/acme/certs/example.com.key", + "-d", + "example.com", + "--dns", + "dns_cf", + "-d", + "example-two.com", + "-d", + "example-three.com", + }, + err: nil, + }, + }, + { + name: "dns single domain no provider", + domains: []string{"example.com"}, + method: "dns", + caBundle: "", + outputFullchainFile: "/data/acme/certs/a.crt", + outputKeyFile: "/data/acme/certs/example.com.key", + dnsProvider: nil, + want: want{ + args: nil, + err: ErrDNSNeedsDNSProvider, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + args, err := buildCertRequestArgs(tt.domains, tt.method, tt.caBundle, tt.outputFullchainFile, tt.outputKeyFile, tt.dnsProvider) + + assert.Equal(t, tt.want.args, args) + assert.Equal(t, tt.want.err, err) + }) + } +} diff --git a/backend/internal/acme/errors.go b/backend/internal/acme/errors.go new file mode 100644 index 00000000..b929ce66 --- /dev/null +++ b/backend/internal/acme/errors.go @@ -0,0 +1,10 @@ +package acme + +import "errors" + +// All errors relating to Acme.sh use +var ( + ErrDNSNeedsDNSProvider = errors.New("RequestCert dns method requires a dns provider") + ErrHTTPHasDNSProvider = errors.New("RequestCert http method does not need a dns provider") + ErrMethodNotSupported = errors.New("RequestCert method not supported") +) diff --git a/backend/internal/api/schema/create_dns_provider.go b/backend/internal/api/schema/create_dns_provider.go index 6da73e72..bc85385e 100644 --- a/backend/internal/api/schema/create_dns_provider.go +++ b/backend/internal/api/schema/create_dns_provider.go @@ -9,17 +9,17 @@ func CreateDNSProvider() string { "type": "object", "additionalProperties": false, "required": [ - "provider_key", "name", + "acme_sh_name", "meta" ], "properties": { - "provider_key": %s, "name": %s, + "acme_sh_name": %s, "meta": { "type": "object" } } } - `, stringMinMax(2, 100), stringMinMax(1, 100)) + `, stringMinMax(1, 100), stringMinMax(4, 50)) } diff --git a/backend/internal/entity/certificate/methods.go b/backend/internal/entity/certificate/methods.go index ba0445f2..e24181be 100644 --- a/backend/internal/entity/certificate/methods.go +++ b/backend/internal/entity/certificate/methods.go @@ -41,6 +41,7 @@ func Create(certificate *Model) (int, error) { expires_on, status, meta, + is_ecc, is_deleted ) VALUES ( :created_on, @@ -54,6 +55,7 @@ func Create(certificate *Model) (int, error) { :expires_on, :status, :meta, + :is_ecc, :is_deleted )`, certificate) @@ -91,6 +93,7 @@ func Update(certificate *Model) error { expires_on = :expires_on, status = :status, meta = :meta, + is_ecc = :is_ecc, is_deleted = :is_deleted WHERE id = :id`, certificate) diff --git a/backend/internal/entity/certificate/model.go b/backend/internal/entity/certificate/model.go index 0f582cd9..397402cc 100644 --- a/backend/internal/entity/certificate/model.go +++ b/backend/internal/entity/certificate/model.go @@ -50,6 +50,7 @@ type Model struct { Status string `json:"status" db:"status" filter:"status,string"` ErrorMessage string `json:"error_message,omitempty" db:"error_message" filter:"error_message,string"` Meta types.JSONB `json:"-" db:"meta"` + IsECC int `json:"is_ecc" db:"is_ecc" filter:"is_ecc,integer"` IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` // Expansions: CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"` @@ -201,7 +202,8 @@ func (m *Model) Request() error { return err } - err = acme.RequestCert(domains, m.Type) + // TODO: fill in blank params + err = acme.RequestCert(domains, m.Type, "", "", "", nil) if err != nil { m.Status = StatusFailed m.ErrorMessage = err.Error() diff --git a/backend/internal/entity/dnsprovider/methods.go b/backend/internal/entity/dnsprovider/methods.go index 1eca9ab9..1c205c17 100644 --- a/backend/internal/entity/dnsprovider/methods.go +++ b/backend/internal/entity/dnsprovider/methods.go @@ -33,16 +33,16 @@ func Create(provider *Model) (int, error) { created_on, modified_on, user_id, - provider_key, name, + acme_sh_name, meta, is_deleted ) VALUES ( :created_on, :modified_on, :user_id, - :provider_key, :name, + :acme_sh_name, :meta, :is_deleted )`, provider) @@ -73,8 +73,8 @@ func Update(provider *Model) error { created_on = :created_on, modified_on = :modified_on, user_id = :user_id, - provider_key = :provider_key, name = :name, + acme_sh_name = :acme_sh_name, meta = :meta, is_deleted = :is_deleted WHERE id = :id`, provider) diff --git a/backend/internal/entity/dnsprovider/model.go b/backend/internal/entity/dnsprovider/model.go index 70eb22cb..c67c4e97 100644 --- a/backend/internal/entity/dnsprovider/model.go +++ b/backend/internal/entity/dnsprovider/model.go @@ -14,14 +14,14 @@ const ( // Model is the user model type Model struct { - ID int `json:"id" db:"id" filter:"id,integer"` - CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` - ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` - UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` - ProviderKey string `json:"provider_key" db:"provider_key" filter:"provider_key,string"` - Name string `json:"name" db:"name" filter:"name,string"` - Meta types.JSONB `json:"meta" db:"meta"` - IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` + ID int `json:"id" db:"id" filter:"id,integer"` + CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,integer"` + ModifiedOn types.DBDate `json:"modified_on" db:"modified_on" filter:"modified_on,integer"` + UserID int `json:"user_id" db:"user_id" filter:"user_id,integer"` + Name string `json:"name" db:"name" filter:"name,string"` + AcmeShName string `json:"acme_sh_name" db:"acme_sh_name" filter:"acme_sh_name,string"` + Meta types.JSONB `json:"meta" db:"meta"` + IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` } func (m *Model) getByQuery(query string, params []interface{}) error { @@ -71,3 +71,41 @@ func (m *Model) Delete() bool { } return true } + +// GetAcmeShEnvVars returns the env vars required for acme.sh dns cert requests +func (m *Model) GetAcmeShEnvVars() ([]string, error) { + envs := make([]string, 0) + switch m.AcmeShName { + + // AWS + case "dns_aws": + envs = []string{ + "AWS_ACCESS_KEY_ID=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"", + "AWS_SECRET_ACCESS_KEY=\"xxxxxxx\"", + } + + // Cloudflare + case "dns_cf": + envs = []string{ + "CF_Key=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"", + "CF_Email=\"xxxx@sss.com\"", + "CF_Token=\"xxxx\"", + "CF_Account_ID=\"xxxx\"", + "CF_Zone_ID=\"xxxx\"", + } + + // DuckDNS + case "dns_duckdns": + envs = []string{ + "DuckDNS_Token=\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"", + } + + // Njalla + case "dns_njalla": + envs = []string{ + "NJALLA_Token=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"", + } + } + + return envs, nil +} diff --git a/backend/internal/entity/dnsprovider/model_test.go b/backend/internal/entity/dnsprovider/model_test.go new file mode 100644 index 00000000..1f12a366 --- /dev/null +++ b/backend/internal/entity/dnsprovider/model_test.go @@ -0,0 +1,70 @@ +package dnsprovider + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestModelGetAcmeShEnvVars(t *testing.T) { + type want struct { + envs []string + err error + } + + tests := []struct { + name string + dnsProvider Model + want want + }{ + { + name: "dns_aws", + dnsProvider: Model{ + AcmeShName: "dns_aws", + }, + want: want{ + envs: []string{ + "AWS_ACCESS_KEY_ID=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"", + "AWS_SECRET_ACCESS_KEY=\"xxxxxxx\"", + }, + err: nil, + }, + }, + { + name: "dns_cf", + dnsProvider: Model{ + AcmeShName: "dns_cf", + }, + want: want{ + envs: []string{ + "CF_Key=\"sdfsdfsdfljlbjkljlkjsdfoiwje\"", + "CF_Email=\"xxxx@sss.com\"", + "CF_Token=\"xxxx\"", + "CF_Account_ID=\"xxxx\"", + "CF_Zone_ID=\"xxxx\"", + }, + err: nil, + }, + }, + { + name: "dns_duckdns", + dnsProvider: Model{ + AcmeShName: "dns_duckdns", + }, + want: want{ + envs: []string{ + "DuckDNS_Token=\"aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee\"", + }, + err: nil, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + envs, err := tt.dnsProvider.GetAcmeShEnvVars() + assert.Equal(t, tt.want.envs, envs) + assert.Equal(t, tt.want.err, err) + }) + } +} diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index 4ec61383..90492c1b 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -11,7 +11,8 @@ ENV GOPROXY=$GOPROXY \ GOPRIVATE=$GOPRIVATE \ S6_LOGGING=0 \ SUPPRESS_NO_CONFIG_WARNING=1 \ - S6_FIX_ATTRS_HIDDEN=1 + S6_FIX_ATTRS_HIDDEN=1 \ + CERT_HOME=/data/acme/ RUN echo "fs.file-max = 65535" > /etc/sysctl.conf diff --git a/docker/docker-compose.dev.yml b/docker/docker-compose.dev.yml index d22ea115..bb32676d 100644 --- a/docker/docker-compose.dev.yml +++ b/docker/docker-compose.dev.yml @@ -23,6 +23,12 @@ services: - ./rootfs/var/www/html:/var/www/html - ../data:/data working_dir: /app + networks: + default: + aliases: + - website1.internal + - website2.internal + - website3.internal stepca: image: nginxproxymanager/testca diff --git a/docker/rootfs/etc/cont-init.d/10-nginx b/docker/rootfs/etc/cont-init.d/10-nginx index edfdaf26..d1aaf652 100755 --- a/docker/rootfs/etc/cont-init.d/10-nginx +++ b/docker/rootfs/etc/cont-init.d/10-nginx @@ -18,7 +18,7 @@ mkdir -p /tmp/nginx/body \ /var/lib/nginx/cache/public \ /var/lib/nginx/cache/private \ /var/cache/nginx/proxy_temp \ - /data/acme + /data/acme/wellknown touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx