mholt / acmez Goto Github PK
View Code? Open in Web Editor NEWPremier ACME client library for Go
Home Page: https://pkg.go.dev/github.com/mholt/acmez/v2
License: Apache License 2.0
Premier ACME client library for Go
Home Page: https://pkg.go.dev/github.com/mholt/acmez/v2
License: Apache License 2.0
I am currently trying to use caddy with a corporate acme endpoint that is behind an AWS ELB.
Due to the round-robin nature of the ELB, I am unable to do a full http challenge going to the same backend server.
We need either https keep-alive or cookie support to allow caddy to connect to the same server at each stage of the request.
Hello! I apologize if this is not the correct repo impacted here. I have an issue that I believe is related to this library:
I have an inhouse-developed batch processing app that allows us to standardize processing of potentially large batches of public certificates.
I noticed in my last few batches that there is an issue when certificates such as the below are BOTH specified in the same batch. The issue is related to the DCV challenge record, I believe.
For example - both requested in sequence one after the other:
*.example.eu.domain.app
example.eu.domain.app
From my perspective, there are potentially 4 different libraries that could be involved: acmez, certmagic, libdns/route53 or the code I've added for the batch processing.
Could you please briefly review this log output I captured and see if you may have an "Aha!" moment? If not... I'm happy to dig deeper on my code assembly and see if I can narrow down the issue. Also I'm currently using an older version of libdns/route53 due to a possible issue with the latest version.
(As a sidenote: I use multiple libdns providers but it seems I occasionally run into a variance with the provider outputs. I've thought about writing a way to validate these outputs. Ideally all providers should accept the same inputs and produce identical-format outputs. A tool could help certify the providers as equivalent.)
2023/08/30 11:56:21 ca.go:236: Issue certificate #5/7 from Digicert: *.example.eu.domain.app [route53]
1.693418181555955e+09 info obtain acquiring lock {"identifier": "*.example.eu.domain.app"}
1.6934181815623481e+09 info obtain lock acquired {"identifier": "*.example.eu.domain.app"}
1.6934181815624409e+09 info obtain obtaining certificate {"identifier": "*.example.eu.domain.app"}
1.693418181639694e+09 info waiting on internal rate limiter {"identifiers": ["*.example.eu.domain.app"], "ca": "https://acme.digicert.com/v2/acme/directory/", "account": "[email protected]"}
1.693418181639717e+09 info done waiting on internal rate limiter {"identifiers": ["*.example.eu.domain.app"], "ca": "https://acme.digicert.com/v2/acme/directory/", "account": "[email protected]"}
1.69341818190844e+09 info acme_client trying to solve challenge {"identifier": "*.example.eu.domain.app", "challenge_type": "dns-01", "ca": "https://acme.digicert.com/v2/acme/directory/"}
1.693418194070356e+09 error acme_client cleaning up solver {"identifier": "*.example.eu.domain.app", "challenge_type": "dns-01", "error": "no memory of presenting a DNS record for \"_acme-challenge.example.eu.domain.app\" (usually OK if presenting also failed)"}
1.6934181940704799e+09 info acme_client authorization finalized {"identifier": "*.example.eu.domain.app", "authz_status": "valid"}
1.6934181940705001e+09 info acme_client validations succeeded; finalizing order {"order": "https://acme.digicert.com/v2/acme/order/__redacted__/LxJadUh43yM1kfS9fUA3UQixZbc-dfbWNCOFxlDlOn0"}
1.693418229275489e+09 info acme_client successfully downloaded available certificate chains {"count": 1, "first_url": "https://acme.digicert.com/v2/acme/cert/__redacted__/12CAC69ADA26CE504BA8C09F7F60AA523E434265"}
1.6934182292897239e+09 info obtain certificate obtained successfully {"identifier": "*.example.eu.domain.app"}
1.693418229289839e+09 info obtain releasing lock {"identifier": "*.example.eu.domain.app"}
1.693418229464159e+09 warn stapling OCSP {"error": "no OCSP stapling for [*.example.eu.domain.app]: parsing OCSP response: ocsp: error from server: unauthorized", "identifiers": ["*.example.eu.domain.app"]}
2023/08/30 11:57:09 ca.go:236: Issue certificate #6/7 from Digicert: example.eu.domain.app [route53]
1.693418229483319e+09 info obtain acquiring lock {"identifier": "example.eu.domain.app"}
1.693418229489676e+09 info obtain lock acquired {"identifier": "example.eu.domain.app"}
1.693418229489938e+09 info obtain obtaining certificate {"identifier": "example.eu.domain.app"}
1.693418229590105e+09 info waiting on internal rate limiter {"identifiers": ["example.eu.domain.app"], "ca": "https://acme.digicert.com/v2/acme/directory/", "account": "[email protected]"}
1.693418229590131e+09 info done waiting on internal rate limiter {"identifiers": ["example.eu.domain.app"], "ca": "https://acme.digicert.com/v2/acme/directory/", "account": "[email protected]"}
1.693418229828408e+09 info acme_client trying to solve challenge {"identifier": "example.eu.domain.app", "challenge_type": "dns-01", "ca": "https://acme.digicert.com/v2/acme/directory/"}
1.693418230415124e+09 error acme_client cleaning up solver {"identifier": "example.eu.domain.app", "challenge_type": "dns-01", "error": "no memory of presenting a DNS record for \"_acme-challenge.example.eu.domain.app\" (usually OK if presenting also failed)"}
1.6934182304475899e+09 error acme_client deactivating authorization {"identifier": "example.eu.domain.app", "authz": "https://acme.digicert.com/v2/acme/authz/__redacted__/R1sy_q1Yqiee_-vI", "error": "attempt 1: https://acme.digicert.com/v2/acme/authz/__redacted__/R1sy_q1Yqiee_-vI: HTTP 400 urn:ietf:params:acme:error:malformed - payload field of the JWS object must be the empty string"}
1.693418230447774e+09 error obtain could not get certificate from issuer {"identifier": "example.eu.domain.app", "issuer": "acme.digicert.com-v2-acme-directory", "error": "[example.eu.domain.app] solving challenges: presenting for challenge: adding temporary record for zone \"eu.example.app.\": InvalidChangeBatch: operation error Route 53: ChangeResourceRecordSets, https response error StatusCode: 400, RequestID: bf7444c3-887f-40ea-b785-71f239086bb6, InvalidChangeBatch: [Tried to create resource record set [name='_acme-challenge.example.eu.domain.app.', type='TXT'] but it already exists] (order=https://acme.digicert.com/v2/acme/order/__redacted__/EpJ5lOMdCripOFtpT4rhyc8SbxJq7fALZMMbcLSQ8sc) (ca=https://acme.digicert.com/v2/acme/directory/)"}
1.693418230447844e+09 info obtain releasing lock {"identifier": "example.eu.domain.app"}
2023/08/30 11:57:10 logic.go:82: example.eu.domain.app: obtaining certificate: [example.eu.domain.app] Obtain: [example.eu.domain.app] solving challenges: presenting for challenge: adding temporary record for zone "eu.example.app.": InvalidChangeBatch: operation error Route 53: ChangeResourceRecordSets, https response error StatusCode: 400, RequestID: bf7444c3-887f-40ea-b785-71f239086bb6, InvalidChangeBatch: [Tried to create resource record set [name='_acme-challenge.example.eu.domain.app.', type='TXT'] but it already exists] (order=https://acme.digicert.com/v2/acme/order/__redacted__/EpJ5lOMdCripOFtpT4rhyc8SbxJq7fALZMMbcLSQ8sc) (ca=https://acme.digicert.com/v2/acme/directory/)
During the test, I found that after the authorization valid passed, it seemed that the solver was not executed when I requested to obtain the certificate again,https://github.com/mholt/acmez/blob/master/client.go#L340 ,Because I didn't save DNS TXT record for the first time,After that, I can't seem to get the record again
If
dns-01
)ObtainCertificate
is called with multiple SAN's (e.g. "a.example.com", "b.example.com", "c.example.com", "d.example.com"
)c.example.com
),then all certificates up to the one that failed will be presented and cleaned up again. No actual solving is done.
This means that if there are 100 SAN's requested and the last one fails, 99 completely useless calls will be done to Present
and CleanUp
(which in turn do more requests to DNS api's etc.)
Here is a example log with replaced domain where the challenge for c.example.com
fails:
[email protected]/client.go:394 trying to solve challenge {"identifier": "a.example.com", "challenge_type": "dns-01", "ca": "https://acme-staging-v02.api.letsencrypt.org/directory"}
daemon/main.go:35 present {"id": "a.example.com", "record": "_acme-challenge.a.example.com", "value": "dJ8PiGOYRZRm9ncUaH9zoV-y4aJqymOCbt9fntmbdAo"}
[email protected]/client.go:394 trying to solve challenge {"identifier": "b.example.com", "challenge_type": "dns-01", "ca": "https://acme-staging-v02.api.letsencrypt.org/directory"}
daemon/main.go:35 present {"id": "b.example.com", "record": "_acme-challenge.b.example.com", "value": "_vJbdqEhAogUIrwOGwiduTWY3vC9ZSrN7_8eQ0kI-78"}
[email protected]/client.go:394 trying to solve challenge {"identifier": "c.example.com", "challenge_type": "dns-01", "ca": "https://acme-staging-v02.api.letsencrypt.org/directory"}
daemon/main.go:35 present {"id": "c.example.com", "record": "_acme-challenge.c.example.com", "value": "ifP-oljAlaX-Zf-yDBxV79mHrABZnXyp1Bx9zX1_LpE"}
[email protected]/client.go:394 trying to solve challenge {"identifier": "d.example.com", "challenge_type": "dns-01", "ca": "https://acme-staging-v02.api.letsencrypt.org/directory"}
daemon/main.go:35 present {"id": "d.example.com", "record": "_acme-challenge.d.example.com", "value": "g3NU4vF2SSNEWWaYHcJ7STO6zU5eQB02drvadyc75UQ"}
daemon/main.go:49 wait {"id": "a.example.com", "record": "_acme-challenge.a.example.com", "value": "dJ8PiGOYRZRm9ncUaH9zoV-y4aJqymOCbt9fntmbdAo"}
[email protected]/client.go:433 challenge accepted {"identifier": "a.example.com", "challenge_type": "dns-01"}
daemon/main.go:49 wait {"id": "b.example.com", "record": "_acme-challenge.b.example.com", "value": "_vJbdqEhAogUIrwOGwiduTWY3vC9ZSrN7_8eQ0kI-78"}
[email protected]/client.go:433 challenge accepted {"identifier": "b.example.com", "challenge_type": "dns-01"}
daemon/main.go:49 wait {"id": "c.example.com", "record": "_acme-challenge.c.example.com", "value": "ifP-oljAlaX-Zf-yDBxV79mHrABZnXyp1Bx9zX1_LpE"}
[email protected]/client.go:433 challenge accepted {"identifier": "c.example.com", "challenge_type": "dns-01"}
daemon/main.go:49 wait {"id": "d.example.com", "record": "_acme-challenge.d.example.com", "value": "g3NU4vF2SSNEWWaYHcJ7STO6zU5eQB02drvadyc75UQ"}
[email protected]/client.go:433 challenge accepted {"identifier": "d.example.com", "challenge_type": "dns-01"}
daemon/main.go:90 cleanup {"id": "a.example.com", "record": "_acme-challenge.a.example.com", "value": "dJ8PiGOYRZRm9ncUaH9zoV-y4aJqymOCbt9fntmbdAo"}
daemon/main.go:90 cleanup {"id": "b.example.com", "record": "_acme-challenge.b.example.com", "value": "_vJbdqEhAogUIrwOGwiduTWY3vC9ZSrN7_8eQ0kI-78"}
daemon/main.go:90 cleanup {"id": "c.example.com", "record": "_acme-challenge.c.example.com", "value": "ifP-oljAlaX-Zf-yDBxV79mHrABZnXyp1Bx9zX1_LpE"}
[email protected]/client.go:512 challenge failed {"identifier": "c.example.com", "challenge_type": "dns-01", "problem": {"type": "urn:ietf:params:acme:error:unauthorized", "title": "", "detail": "Incorrect TXT record \"ifP-oljAlaX-Zf-yDBxV79mHrABZnXyp1Bx9zX1_LpE_fail\" found at _acme-challenge.c.example.com", "instance": "", "subproblems": []}}
github.com/mholt/acmez.(*Client).pollAuthorization
/home/simon/go/pkg/mod/github.com/mholt/[email protected]/client.go:512
github.com/mholt/acmez.(*Client).solveChallenges
/home/simon/go/pkg/mod/github.com/mholt/[email protected]/client.go:368
github.com/mholt/acmez.(*Client).ObtainCertificateUsingCSR
/home/simon/go/pkg/mod/github.com/mholt/[email protected]/client.go:127
github.com/mholt/acmez.(*Client).ObtainCertificate
/home/simon/go/pkg/mod/github.com/mholt/[email protected]/client.go:235
...
daemon/main.go:90 cleanup {"id": "d.example.com", "record": "_acme-challenge.d.example.com", "value": "g3NU4vF2SSNEWWaYHcJ7STO6zU5eQB02drvadyc75UQ"}
[email protected]/client.go:143 validating authorization {"identifier": "c.example.com", "problem": {"type": "urn:ietf:params:acme:error:unauthorized", "title": "", "detail": "Incorrect TXT record \"ifP-oljAlaX-Zf-yDBxV79mHrABZnXyp1Bx9zX1_LpE_fail\" found at _acme-challenge.c.example.com", "instance": "", "subproblems": []}, "order": "https://acme-staging-v02.api.letsencrypt.org/acme/order/38834018/1399577638", "attempt": 1, "max_attempts": 3}
github.com/mholt/acmez.(*Client).ObtainCertificateUsingCSR
/home/simon/go/pkg/mod/github.com/mholt/[email protected]/client.go:143
github.com/mholt/acmez.(*Client).ObtainCertificate
/home/simon/go/pkg/mod/github.com/mholt/[email protected]/client.go:235
...
[email protected]/client.go:461 no solver configured {"challenge_type": "tls-alpn-01"}
[email protected]/client.go:461 no solver configured {"challenge_type": "http-01"}
[email protected]/client.go:394 trying to solve challenge {"identifier": "a.example.com", "challenge_type": "dns-01", "ca": "https://acme-staging-v02.api.letsencrypt.org/directory"}
daemon/main.go:35 present {"id": "a.example.com", "record": "_acme-challenge.a.example.com", "value": "qHh2Rep4kRSFiJEQ5x4vKS6OwDz_3A4vbo2qnsCCUY0"}
[email protected]/client.go:461 no solver configured {"challenge_type": "tls-alpn-01"}
[email protected]/client.go:461 no solver configured {"challenge_type": "http-01"}
[email protected]/client.go:394 trying to solve challenge {"identifier": "b.example.com", "challenge_type": "dns-01", "ca": "https://acme-staging-v02.api.letsencrypt.org/directory"}
daemon/main.go:35 present {"id": "b.example.com", "record": "_acme-challenge.b.example.com", "value": "DmvYmPXjLpSkcdXgn7f_IoTjYqwJmsgIBR9AFyuSxY4"}
[email protected]/client.go:461 no solver configured {"challenge_type": "tls-alpn-01"}
[email protected]/client.go:461 no solver configured {"challenge_type": "http-01"}
daemon/main.go:90 cleanup {"id": "a.example.com", "record": "_acme-challenge.a.example.com", "value": "qHh2Rep4kRSFiJEQ5x4vKS6OwDz_3A4vbo2qnsCCUY0"}
daemon/main.go:90 cleanup {"id": "b.example.com", "record": "_acme-challenge.b.example.com", "value": "DmvYmPXjLpSkcdXgn7f_IoTjYqwJmsgIBR9AFyuSxY4"}
obtaining certificate: solving challenges: c.example.com: no solvers available for remaining challenges (configured=[dns-01] offered=[http-01 dns-01 tls-alpn-01] remaining=[http-01 tls-alpn-01]) (order=https://acme-staging-v02.api.letsencrypt.org/acme/order/38834018/1399583088)
After the challenge failed, a.example.com
and b.example.com
are presented and cleaned up without any attempted solve.
I'm trying to update my API that uses acmez to use crypto/ecdh instead of crypto/elliptic
. See this issue for more information about the rationale for deprecating crypto/elliptic
. And this comment:
// Direct use of this package is deprecated, beyond the P224(), P256(), P384(),
// and P521() values necessary to use the crypto/ecdsa package. Most other uses
// should migrate to the more efficient and safer crypto/ecdh package.
The problem is: it doesn't implement signer so I can't use it for my PrivateKey
in my acme.Account
struct:
accountPrivateKey, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return resp, fmt.Errorf("generating account key: %v", err)
}
// Doesn't work because accountPrivateKey doesn't implement Signer
account := acme.Account{
Contact: []string{req.AccountContactEmailUrl},
TermsOfServiceAgreed: true,
PrivateKey: accountPrivateKey,
}
Do you have any suggestions for this? Should I type alias it and implement Signer
or something along those lines?
检测到 mholt/acmez 一共引入了29个开源组件,存在2个漏洞
漏洞标题:go-yaml < 2.2.8拒绝服务漏洞
漏洞编号:CVE-2019-11254
漏洞描述:gopkg.in/yaml.v2是go语言中用于处理yaml格式的包。
在2.2.8之前的版本中,处理恶意的yaml数据时,会导致CPU资源耗尽。
漏洞由Kubernetes开发者在fuzz测试中发现并提交修复补丁。
国家漏洞库信息:https://www.cnvd.org.cn/flaw/show/CNVD-2020-35519
影响范围:(∞, 2.2.8)
最小修复版本:2.2.8
缺陷组件引入路径:github.com/mholt/acmez@->go.uber.org/[email protected]>github.com/stretchr/[email protected]>gopkg.in/[email protected]
另外还有2个漏洞,详细报告:https://mofeisec.com/jr?p=n6cb08
I have been experimenting with certmagic and a let's encrypt Pebble server and have gottne things to work overall but there I had been getting this error:
Nov 30 09:58:26 dsdev ds-host[1363]: 1.6698311064858541e+09 error obtain could not get certificate from issuer {"identifier": "sub.domain.develop", "issuer": "127.0.0.1:14000-dir", "error": "[sub.domain.develop] finalizing order https://127.0.0.1:14000/my-order/WkaEgtUJEiIT4hysbU7u5WbAHQiH9wJh8afKlt_iAEU: response had invalid Retry-After header: Wed, 30 Nov 2022 17:58:31 GMT (ca=https://127.0.0.1:14000/dir)"}
After some digging I found that Pebble recently added some Retry-After
headers: letsencrypt/pebble@6538ad0
It turns out that it flips a coin and sends the header either as seconds or as a full date time. This is per-spec: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
Looking at where the error is thrown shows the problem: acmez expects only seconds and doesn't handle the full date-time case:
Lines 376 to 383 in d79cc20
I'm working on a PR.
acmez v1.1.0 (bundled via certmagic v0.17.2)
We're trying to obtain an SSL certificate on demand with certmagic via the ZeroSSL ACME issuer.
We've installed certmagic as documented and are running successfully for thousands of domains. A small percentage of our SSL requests trigger the error Obtain: [XXXX.com] creating new order: attempt 1: https://acme.zerossl.com/v2/DV90/newOrder: HTTP 400 urn:ietf:params:acme:error:malformed - Invalid signature on JWS request
We expect that the SSL certificate would be issued.
I'm unsure what the best fix be. It might be a race condition, having multiple orders per domain.
We use the S3 storage adapter for certmagic which does support locks via placing files in the bucket but isn't atomic.
{"level":"error","ts":1680599702.936518,"logger":"obtain","caller":"[email protected]/async.go:117","msg":"will retry","component":"acme","error":"[XXXX.com] Obtain: [XXXX.com] creating new order: attempt 1: https://acme.zerossl.com/v2/DV90/newOrder: HTTP 400 urn:ietf:params:acme:error:malformed - Invalid signature on
JWS request (ca=https://acme.zerossl.com/v2/DV90)","attempt":2,"retrying_in":120,"elapsed":66.086730116,"max_duration":2592000,"stacktrace":"github.com/caddyserver/certmagic.doWithRetry\n\t/go/pkg/mod/github.com/caddyserver/[email protected]/async.go:117\ngithub.com/caddyserver/certmagic.(*Config).obtainCert\n\t/go/pkg/mod/github.com/caddyserver/[email protected]/config.go:611\ngithub.com/caddyserver/certmagic.(*Config).ObtainCertAsync\n\t/go/pkg/mod/github.com/caddyserver/[email protected]/config.go:462\ngithub.com/caddyserver/certmagic.(*Config).obtainOnDemandCertificate\n\t/go/pkg/mod/github.com/caddyserver/[email protected]/handshake.go:447\ngithub.com/caddyserver/certmagic.(*Config).getCertDuringHandshake\n\t/go/pkg/mod/github.com/caddyserver/[email protected]/handshake.go:314\ngithub.com/caddyserver/certmagic.(*Config).GetCertificate\n\t/go/pkg/mod/github.com/caddyserver/[email protected]/handshake.go:77\ncrypto/tls.(*Config).getCertificate\n\t/usr/local/go/src/crypto/tls/common.go:1079\ncrypto/tls.(*serverHandshakeState).processClientHello\n\t/usr/local/go/src/crypto/tls/handshake_server.go:229\ncrypto/tls.(*serverHandshakeState).handshake\n\t/usr/local/go/src/crypto/tls/handshake_server.go:67\ncrypto/tls.(*Conn).serverHandshake\n\t/usr/local/go/src/crypto/tls/handshake_server.go:61\ncrypto/tls.(*Conn).handshakeContext\n\t/usr/local/go/src/crypto/tls/conn.go:1491\ncrypto/tls.(*Conn).HandshakeContext\n\t/usr/local/go/src/crypto/tls/conn.go:1434\nnet/http.(*conn).serve\n\t/usr/local/go/src/net/http/server.go:1877"}
In https://github.com/mholt/acmez/blob/master/client.go#L114 , does that mean that it will only wait 1 second for LetsEncrypt to respond?
We have sometimes waited for 10 or 20 seconds. :( Can we send a pull request to change that one line, or am I misunderstanding that line?
Thanks for an awesome lib!
I am currently trying to use caddy with a corporate acme endpoint that is behind an AWS ELB.
Due to the round-robin nature of the ELB, I am unable to do a full http challenge going to the same backend server.
I hope we can add a cookie jar to the acme client, so that ELB sticky cookies can route the request back to the same backend server.
👋 If this repo is forked from Lego, should it also be licensed under MIT to match https://github.com/go-acme/lego/blob/master/LICENSE ? If not, what is the license?
Thanks!
client := acmez.Client{
Client: &acme.Client{
Directory: constants.DefaultCA,
},
ChallengeSolvers: map[string]acmez.Solver{
acme.ChallengeTypeDNS01: &acme_issuer.DNS01Solver{
PropagationTimeout: time.Minute * 15,
PollingInterval: time.Second * 10,
TTL: constants.DefaultDnsRecordTTL,
DNSProvider: dnsProvider,
},
},
}
domains:=[]string{"*.example.com","example.com"}
log.Println("-----")
log.Printf("%+v\n", domains)
certs, err := client.ObtainCertificate(context.Background(), account.AcmeAccount, certPrivateKey, domains)
if err != nil {
log.Println(err)
return nil, fmt.Errorf("obtaining certificate: %v", err)
}
log.Println("====")
it use one dns provider, cloudflare.
when i try one domain, it's works fine
domains:=[]string{"*.example.com"}
or domains:=[]string{"example.com"}
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.