Work on acme.sh hander

and dns providers
This commit is contained in:
Jamie Curnow
2021-08-19 22:33:01 +10:00
parent 339ee13346
commit 556f8b773b
19 changed files with 518 additions and 81 deletions

View File

@@ -1768,7 +1768,7 @@ _inithttp() {
if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then if [ -z "$ACME_HTTP_NO_REDIRECTS" ]; then
_ACME_CURL="$_ACME_CURL -L " _ACME_CURL="$_ACME_CURL -L "
fi fi
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then if [ "$DEBUG" ] && [ "$DEBUG" -ge 2 ]; then
_CURL_DUMP="$(_mktemp)" _CURL_DUMP="$(_mktemp)"
_ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP " _ACME_CURL="$_ACME_CURL --trace-ascii $_CURL_DUMP "
fi fi
@@ -1808,6 +1808,8 @@ _inithttp() {
} }
_HTTP_MAX_RETRY=8
# body url [needbase64] [POST|PUT|DELETE] [ContentType] # body url [needbase64] [POST|PUT|DELETE] [ContentType]
_post() { _post() {
body="$1" body="$1"
@@ -1815,6 +1817,33 @@ _post() {
needbase64="$3" needbase64="$3"
httpmethod="$4" httpmethod="$4"
_postContentType="$5" _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 if [ -z "$httpmethod" ]; then
httpmethod="POST" httpmethod="POST"
@@ -1866,7 +1895,9 @@ _post() {
fi fi
_ret="$?" _ret="$?"
if [ "$_ret" != "0" ]; then if [ "$_ret" != "0" ]; then
if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret" _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $_ret"
fi
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
_err "Here is the curl dump log:" _err "Here is the curl dump log:"
_err "$(cat "$_CURL_DUMP")" _err "$(cat "$_CURL_DUMP")"
@@ -1922,8 +1953,10 @@ _post() {
_debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later."
fi fi
if [ "$_ret" != "0" ]; then if [ "$_ret" != "0" ]; then
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" _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $_ret"
fi fi
fi
_sed_i "s/^ *//g" "$HTTP_HEADER" _sed_i "s/^ *//g" "$HTTP_HEADER"
else else
_ret="$?" _ret="$?"
@@ -1936,13 +1969,38 @@ _post() {
# url getheader timeout # url getheader timeout
_get() { _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 _debug GET
url="$1" url="$1"
onlyheader="$2" onlyheader="$2"
t="$3" t="$3"
displayError="$4"
_debug url "$url" _debug url "$url"
_debug "timeout=$t" _debug "timeout=$t"
_debug "displayError" "$displayError"
_inithttp _inithttp
if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then if [ "$_ACME_CURL" ] && [ "${ACME_USE_WGET:-0}" = "0" ]; then
@@ -1961,7 +2019,9 @@ _get() {
fi fi
ret=$? ret=$?
if [ "$ret" != "0" ]; then if [ "$ret" != "0" ]; then
if [ -z "$displayError" ] || [ "$displayError" = "0" ]; then
_err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret" _err "Please refer to https://curl.haxx.se/libcurl/c/libcurl-errors.html for error code: $ret"
fi
if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then if [ "$DEBUG" ] && [ "$DEBUG" -ge "2" ]; then
_err "Here is the curl dump log:" _err "Here is the curl dump log:"
_err "$(cat "$_CURL_DUMP")" _err "$(cat "$_CURL_DUMP")"
@@ -1987,8 +2047,10 @@ _get() {
_debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later." _debug "wget returns 8, the server returns a 'Bad request' response, lets process the response later."
fi fi
if [ "$ret" != "0" ]; then if [ "$ret" != "0" ]; then
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" _err "Please refer to https://www.gnu.org/software/wget/manual/html_node/Exit-Status.html for error code: $ret"
fi fi
fi
else else
ret=$? ret=$?
_err "Neither curl nor wget is found, can not do GET." _err "Neither curl nor wget is found, can not do GET."
@@ -3925,7 +3987,7 @@ _ns_lookup_ali() {
} }
_ns_is_available_dp() { _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 return 0
else else
return 1 return 1
@@ -4145,6 +4207,10 @@ issue() {
if [ -z "$_ACME_IS_RENEW" ]; then if [ -z "$_ACME_IS_RENEW" ]; then
_initpath "$_main_domain" "$_key_length" _initpath "$_main_domain" "$_key_length"
mkdir -p "$DOMAIN_PATH" mkdir -p "$DOMAIN_PATH"
else
Le_OrderFinalize=""
Le_LinkOrder=""
Le_LinkCert=""
fi fi
if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then if _hasfield "$_web_roots" "$W_DNS" && [ -z "$FORCE_DNS_MANUAL" ]; then
@@ -4712,26 +4778,13 @@ $_authorizations_map"
return 1 return 1
fi 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" _debug2 original "$response"
response="$(echo "$response" | _normalizeJson)" response="$(echo "$response" | _normalizeJson)"
_debug2 response "$response" _debug2 response "$response"
status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"') status=$(echo "$response" | _egrep_o '"status":"[^"]*' | cut -d : -f 2 | tr -d '"')
_debug2 status "$status"
if _contains "$status" "invalid"; then if _contains "$status" "invalid"; then
error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')" error="$(echo "$response" | _egrep_o '"error":\{[^\}]*')"
_debug2 error "$error" _debug2 error "$error"
@@ -4763,9 +4816,9 @@ $_authorizations_map"
fi fi
if [ "$status" = "pending" ]; then 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 elif [ "$status" = "processing" ]; then
_info "Processing" _info "Processing, The CA is processing your order, please just wait. ($waittimes/$MAX_RETRY_TIMES)"
else else
_err "$d:Verify error:$response" _err "$d:Verify error:$response"
_clearupwebbroot "$_currentRoot" "$removelevel" "$token" _clearupwebbroot "$_currentRoot" "$removelevel" "$token"
@@ -4773,7 +4826,19 @@ $_authorizations_map"
_on_issue_err "$_post_hook" "$vlist" _on_issue_err "$_post_hook" "$vlist"
return 1 return 1
fi 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
done done

View File

@@ -7,8 +7,8 @@
"created_on", "created_on",
"modified_on", "modified_on",
"user_id", "user_id",
"provider_key",
"name", "name",
"acme_sh_name",
"meta" "meta"
], ],
"properties": { "properties": {
@@ -28,16 +28,16 @@
"type": "integer", "type": "integer",
"minimum": 1 "minimum": 1
}, },
"provider_key": {
"type": "string",
"minLength": 1,
"maxLength": 100
},
"name": { "name": {
"type": "string", "type": "string",
"minLength": 1, "minLength": 1,
"maxLength": 100 "maxLength": 100
}, },
"acme_sh_name": {
"type": "string",
"minLength": 4,
"maxLength": 50
},
"meta": { "meta": {
"type": "object" "type": "object"
} }

View File

@@ -67,12 +67,11 @@
"created_on": 1602593653, "created_on": 1602593653,
"modified_on": 1602593653, "modified_on": 1602593653,
"user_id": 1, "user_id": 1,
"provider_key": "route53",
"name": "Route53", "name": "Route53",
"acme_sh_name": "dns_aws",
"meta": { "meta": {
"access_key": "abc123", "AWS_ACCESS_KEY_ID": "abc123",
"access_secret": "def098", "AWS_SECRET_ACCESS_KEY": "def098"
"zone_id": "ABC123"
} }
} }
] ]

View File

@@ -36,12 +36,11 @@
"created_on": 1602593653, "created_on": 1602593653,
"modified_on": 1602593653, "modified_on": 1602593653,
"user_id": 1, "user_id": 1,
"provider_key": "route53",
"name": "Route53", "name": "Route53",
"acme_sh_name": "dns_aws",
"meta": { "meta": {
"access_key": "abc123", "AWS_ACCESS_KEY_ID": "abc123",
"access_secret": "def098", "AWS_SECRET_ACCESS_KEY": "def098"
"zone_id": "ABC123"
} }
} }
} }

View File

@@ -40,12 +40,11 @@
"created_on": 1602593653, "created_on": 1602593653,
"modified_on": 1602593653, "modified_on": 1602593653,
"user_id": 1, "user_id": 1,
"provider_key": "route53",
"name": "Route53", "name": "Route53",
"acme_sh_name": "dns_aws",
"meta": { "meta": {
"access_key": "abc123", "AWS_ACCESS_KEY_ID": "abc123",
"access_secret": "def098", "AWS_SECRET_ACCESS_KEY": "def098"
"zone_id": "ABC123"
} }
} }
} }

View File

@@ -50,12 +50,11 @@
"created_on": 1602593653, "created_on": 1602593653,
"modified_on": 1602593653, "modified_on": 1602593653,
"user_id": 1, "user_id": 1,
"provider_key": "route53",
"name": "Route53", "name": "Route53",
"acme_sh_name": "dns_aws",
"meta": { "meta": {
"access_key": "abc123", "AWS_ACCESS_KEY_ID": "abc123",
"access_secret": "def098", "AWS_SECRET_ACCESS_KEY": "def098"
"zone_id": "ABC123"
} }
} }
} }

View File

@@ -70,8 +70,8 @@ CREATE TABLE IF NOT EXISTS `dns_provider`
created_on INTEGER NOT NULL DEFAULT 0, created_on INTEGER NOT NULL DEFAULT 0,
modified_on INTEGER NOT NULL DEFAULT 0, modified_on INTEGER NOT NULL DEFAULT 0,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
provider_key TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
acme_sh_name TEXT NOT NULL,
meta TEXT NOT NULL, meta TEXT NOT NULL,
is_deleted INTEGER NOT NULL DEFAULT 0, is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id) 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 status TEXT NOT NULL, -- ready,requesting,failed,provided
error_message text NOT NULL DEFAULT "", error_message text NOT NULL DEFAULT "",
meta TEXT NOT NULL, meta TEXT NOT NULL,
is_ecc INTEGER NOT NULL DEFAULT 0,
is_deleted INTEGER NOT NULL DEFAULT 0, is_deleted INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES user (id), FOREIGN KEY (user_id) REFERENCES user (id),
FOREIGN KEY (certificate_authority_id) REFERENCES certificate_authority (id), FOREIGN KEY (certificate_authority_id) REFERENCES certificate_authority (id),

View File

@@ -1,5 +1,8 @@
package acme package acme
// Some light reading:
// https://github.com/acmesh-official/acme.sh/wiki/How-to-issue-a-cert
import ( import (
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -10,6 +13,7 @@ import (
"npm/embed" "npm/embed"
"npm/internal/config" "npm/internal/config"
"npm/internal/entity/dnsprovider"
"npm/internal/logger" "npm/internal/logger"
) )
@@ -17,7 +21,7 @@ var acmeShFile string
// GetAcmeShVersion will return the acme.sh script version // GetAcmeShVersion will return the acme.sh script version
func GetAcmeShVersion() string { func GetAcmeShVersion() string {
if r, err := shExec("--version"); err == nil { if r, err := shExec([]string{"--version"}, nil); err == nil {
// modify the output // modify the output
r = strings.Trim(r, "\n") r = strings.Trim(r, "\n")
v := strings.Split(r, "\n") v := strings.Split(r, "\n")
@@ -27,7 +31,7 @@ func GetAcmeShVersion() string {
} }
// shExec executes the acme.sh with arguments // 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) { if _, err := os.Stat(acmeShFile); os.IsNotExist(err) {
e := fmt.Errorf("%s does not exist", acmeShFile) e := fmt.Errorf("%s does not exist", acmeShFile)
logger.Error("AcmeShError", e) logger.Error("AcmeShError", e)
@@ -37,6 +41,8 @@ func shExec(args ...string) (string, error) {
logger.Debug("CMD: %s %v", acmeShFile, args) logger.Debug("CMD: %s %v", acmeShFile, args)
// nolint: gosec // nolint: gosec
c := exec.Command(acmeShFile, args...) c := exec.Command(acmeShFile, args...)
c.Env = envs
b, e := c.Output() b, e := c.Output()
if e != nil { if e != nil {
@@ -65,26 +71,22 @@ func WriteAcmeSh() {
} }
// RequestCert does all the heavy lifting // RequestCert does all the heavy lifting
func RequestCert(domains []string, method string) error { func RequestCert(domains []string, method, caBundle, outputFullchainFile, outputKeyFile string, dnsProvider *dnsprovider.Model) error {
args := []string{"--issue"} // TODO log file location configurable
args, err := buildCertRequestArgs(domains, method, caBundle, outputFullchainFile, outputKeyFile, dnsProvider)
webroot := "/home/wwwroot/example.com" if err != nil {
return err
// Add domains to args
for _, domain := range domains {
args = append(args, "-d", domain)
} }
switch method { envs := make([]string, 0)
// case "dns": if dnsProvider != nil {
case "http": envs, err = dnsProvider.GetAcmeShEnvVars()
args = append(args, "-w", webroot) if err != nil {
return err
default: }
return fmt.Errorf("RequestCert method not supported: %s", method)
} }
ret, err := shExec(args...) ret, err := shExec(args, envs)
if err != nil { if err != nil {
return err return err
} }
@@ -93,3 +95,56 @@ func RequestCert(domains []string, method string) error {
return nil 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
}

View File

@@ -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)
})
}
}

View File

@@ -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")
)

View File

@@ -9,17 +9,17 @@ func CreateDNSProvider() string {
"type": "object", "type": "object",
"additionalProperties": false, "additionalProperties": false,
"required": [ "required": [
"provider_key",
"name", "name",
"acme_sh_name",
"meta" "meta"
], ],
"properties": { "properties": {
"provider_key": %s,
"name": %s, "name": %s,
"acme_sh_name": %s,
"meta": { "meta": {
"type": "object" "type": "object"
} }
} }
} }
`, stringMinMax(2, 100), stringMinMax(1, 100)) `, stringMinMax(1, 100), stringMinMax(4, 50))
} }

View File

@@ -41,6 +41,7 @@ func Create(certificate *Model) (int, error) {
expires_on, expires_on,
status, status,
meta, meta,
is_ecc,
is_deleted is_deleted
) VALUES ( ) VALUES (
:created_on, :created_on,
@@ -54,6 +55,7 @@ func Create(certificate *Model) (int, error) {
:expires_on, :expires_on,
:status, :status,
:meta, :meta,
:is_ecc,
:is_deleted :is_deleted
)`, certificate) )`, certificate)
@@ -91,6 +93,7 @@ func Update(certificate *Model) error {
expires_on = :expires_on, expires_on = :expires_on,
status = :status, status = :status,
meta = :meta, meta = :meta,
is_ecc = :is_ecc,
is_deleted = :is_deleted is_deleted = :is_deleted
WHERE id = :id`, certificate) WHERE id = :id`, certificate)

View File

@@ -50,6 +50,7 @@ type Model struct {
Status string `json:"status" db:"status" filter:"status,string"` Status string `json:"status" db:"status" filter:"status,string"`
ErrorMessage string `json:"error_message,omitempty" db:"error_message" filter:"error_message,string"` ErrorMessage string `json:"error_message,omitempty" db:"error_message" filter:"error_message,string"`
Meta types.JSONB `json:"-" db:"meta"` 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"` IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
// Expansions: // Expansions:
CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"` CertificateAuthority *certificateauthority.Model `json:"certificate_authority,omitempty"`
@@ -201,7 +202,8 @@ func (m *Model) Request() error {
return err return err
} }
err = acme.RequestCert(domains, m.Type) // TODO: fill in blank params
err = acme.RequestCert(domains, m.Type, "", "", "", nil)
if err != nil { if err != nil {
m.Status = StatusFailed m.Status = StatusFailed
m.ErrorMessage = err.Error() m.ErrorMessage = err.Error()

View File

@@ -33,16 +33,16 @@ func Create(provider *Model) (int, error) {
created_on, created_on,
modified_on, modified_on,
user_id, user_id,
provider_key,
name, name,
acme_sh_name,
meta, meta,
is_deleted is_deleted
) VALUES ( ) VALUES (
:created_on, :created_on,
:modified_on, :modified_on,
:user_id, :user_id,
:provider_key,
:name, :name,
:acme_sh_name,
:meta, :meta,
:is_deleted :is_deleted
)`, provider) )`, provider)
@@ -73,8 +73,8 @@ func Update(provider *Model) error {
created_on = :created_on, created_on = :created_on,
modified_on = :modified_on, modified_on = :modified_on,
user_id = :user_id, user_id = :user_id,
provider_key = :provider_key,
name = :name, name = :name,
acme_sh_name = :acme_sh_name,
meta = :meta, meta = :meta,
is_deleted = :is_deleted is_deleted = :is_deleted
WHERE id = :id`, provider) WHERE id = :id`, provider)

View File

@@ -18,8 +18,8 @@ type Model struct {
CreatedOn types.DBDate `json:"created_on" db:"created_on" filter:"created_on,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"` 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"` 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"` 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"` Meta types.JSONB `json:"meta" db:"meta"`
IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"` IsDeleted bool `json:"is_deleted,omitempty" db:"is_deleted"`
} }
@@ -71,3 +71,41 @@ func (m *Model) Delete() bool {
} }
return true 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
}

View File

@@ -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)
})
}
}

View File

@@ -11,7 +11,8 @@ ENV GOPROXY=$GOPROXY \
GOPRIVATE=$GOPRIVATE \ GOPRIVATE=$GOPRIVATE \
S6_LOGGING=0 \ S6_LOGGING=0 \
SUPPRESS_NO_CONFIG_WARNING=1 \ 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 RUN echo "fs.file-max = 65535" > /etc/sysctl.conf

View File

@@ -23,6 +23,12 @@ services:
- ./rootfs/var/www/html:/var/www/html - ./rootfs/var/www/html:/var/www/html
- ../data:/data - ../data:/data
working_dir: /app working_dir: /app
networks:
default:
aliases:
- website1.internal
- website2.internal
- website3.internal
stepca: stepca:
image: nginxproxymanager/testca image: nginxproxymanager/testca

View File

@@ -18,7 +18,7 @@ mkdir -p /tmp/nginx/body \
/var/lib/nginx/cache/public \ /var/lib/nginx/cache/public \
/var/lib/nginx/cache/private \ /var/lib/nginx/cache/private \
/var/cache/nginx/proxy_temp \ /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 touch /var/log/nginx/error.log && chmod 777 /var/log/nginx/error.log && chmod -R 777 /var/cache/nginx