Compare commits
No commits in common. "master" and "0.2.7" have entirely different histories.
1210 changed files with 54912 additions and 149954 deletions
157
.drone.yml
157
.drone.yml
|
|
@ -1,70 +1,89 @@
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: build-linux
|
name: cleanup-before
|
||||||
|
|
||||||
environment:
|
|
||||||
GOOS: linux
|
|
||||||
GOOPTIONS: -mod=vendor
|
|
||||||
SRCFILES: cmd/pki/*.go
|
|
||||||
PROJECTNAME: pki
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build-linux-amd64
|
- name: clean
|
||||||
image: golang
|
image: alpine
|
||||||
commands:
|
commands:
|
||||||
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
- rm -rf /build/*
|
||||||
environment:
|
volumes:
|
||||||
GOARCH: amd64
|
- name: build
|
||||||
|
path: /build
|
||||||
when:
|
when:
|
||||||
event:
|
event: tag
|
||||||
exclude:
|
|
||||||
- tag
|
volumes:
|
||||||
- name: build-linux-arm64
|
- name: build
|
||||||
image: golang
|
host:
|
||||||
commands:
|
path: /tmp/pki/build
|
||||||
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
|
||||||
environment:
|
|
||||||
GOARCH: arm64
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
---
|
||||||
kind: pipeline
|
kind: pipeline
|
||||||
type: docker
|
type: docker
|
||||||
name: gitea-release-linux
|
name: default-linux-amd64
|
||||||
|
|
||||||
environment:
|
|
||||||
GOOS: linux
|
|
||||||
GOOPTIONS: -mod=vendor
|
|
||||||
SRCFILES: cmd/pki/*.go
|
|
||||||
PROJECTNAME: pki
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: build-linux-amd64
|
- name: build
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
- ./ci-build.sh build
|
||||||
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
|
|
||||||
- echo $PROJECTNAME $DRONE_TAG > VERSION
|
|
||||||
environment:
|
environment:
|
||||||
|
GOOS: linux
|
||||||
GOARCH: amd64
|
GOARCH: amd64
|
||||||
when:
|
volumes:
|
||||||
event:
|
- name: build
|
||||||
- tag
|
path: /build
|
||||||
- name: build-linux-arm64
|
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
host:
|
||||||
|
path: /tmp/pki/build
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- cleanup-before
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: default-linux-arm64
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: build
|
||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- go build -o $PROJECTNAME $GOOPTIONS $SRCFILES
|
- ./ci-build.sh build
|
||||||
- tar -czvf $PROJECTNAME-$DRONE_TAG-$GOOS-$GOARCH.tar.gz $PROJECTNAME
|
|
||||||
- echo $PROJECTNAME $DRONE_TAG > VERSION
|
|
||||||
environment:
|
environment:
|
||||||
|
GOOS: linux
|
||||||
GOARCH: arm64
|
GOARCH: arm64
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /build
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
host:
|
||||||
|
path: /tmp/pki/build
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- cleanup-before
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: gitea-release
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: move
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- mv build/* ./
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /drone/src/build
|
||||||
when:
|
when:
|
||||||
event:
|
event: tag
|
||||||
- tag
|
|
||||||
- name: release
|
- name: release
|
||||||
image: plugins/gitea-release
|
image: plugins/gitea-release
|
||||||
settings:
|
settings:
|
||||||
|
|
@ -76,6 +95,50 @@ steps:
|
||||||
- sha256
|
- sha256
|
||||||
- sha512
|
- sha512
|
||||||
title: VERSION
|
title: VERSION
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /drone/src/build
|
||||||
when:
|
when:
|
||||||
event:
|
event: tag
|
||||||
- tag
|
- name: ls
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- find .
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /drone/src/build
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
host:
|
||||||
|
path: /tmp/pki/build
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- default-linux-amd64
|
||||||
|
- default-linux-arm64
|
||||||
|
|
||||||
|
---
|
||||||
|
kind: pipeline
|
||||||
|
type: docker
|
||||||
|
name: cleanup-after
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: clean
|
||||||
|
image: alpine
|
||||||
|
commands:
|
||||||
|
- rm -rf /build/*
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
path: /build
|
||||||
|
when:
|
||||||
|
event: tag
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
- name: build
|
||||||
|
host:
|
||||||
|
path: /tmp/pki/build
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- gitea-release
|
||||||
|
|
|
||||||
16
.github/actions/builder/action.yml
vendored
16
.github/actions/builder/action.yml
vendored
|
|
@ -1,16 +0,0 @@
|
||||||
---
|
|
||||||
name: "builder"
|
|
||||||
description: "builder"
|
|
||||||
inputs:
|
|
||||||
steps:
|
|
||||||
description: "commands"
|
|
||||||
require: true
|
|
||||||
default: ""
|
|
||||||
outputs: {}
|
|
||||||
|
|
||||||
runs:
|
|
||||||
using: "docker"
|
|
||||||
image: "docker://golang:1.25"
|
|
||||||
args:
|
|
||||||
- ${{ inputs.steps }}
|
|
||||||
entrypoint: /bin/sh -c
|
|
||||||
39
.github/workflows/build.yml
vendored
39
.github/workflows/build.yml
vendored
|
|
@ -1,39 +0,0 @@
|
||||||
---
|
|
||||||
name: build & test
|
|
||||||
|
|
||||||
'on':
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- '*'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on:
|
|
||||||
- linux
|
|
||||||
- amd64
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
container:
|
|
||||||
image: ghcr.io/catthehacker/ubuntu:act-24.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
- name: Cache
|
|
||||||
id: pki-cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: go-build
|
|
||||||
key: pki-${{ hashFiles('**/go.mod','**/go.sum') }}
|
|
||||||
- name: Build
|
|
||||||
uses: ./.github/actions/builder
|
|
||||||
with:
|
|
||||||
steps: |
|
|
||||||
go generate cmd/pki/*.go
|
|
||||||
go build -mod=vendor -o pki -v cmd/pki/*.go
|
|
||||||
env:
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: amd64
|
|
||||||
GOCACHE: ${{ forgejo.workspace }}/go-build
|
|
||||||
VERSION: ${{ forgejo.ref_name }}
|
|
||||||
90
.github/workflows/release.yml
vendored
90
.github/workflows/release.yml
vendored
|
|
@ -1,90 +0,0 @@
|
||||||
---
|
|
||||||
name: build & release
|
|
||||||
|
|
||||||
'on':
|
|
||||||
push:
|
|
||||||
tags:
|
|
||||||
- '*'
|
|
||||||
pull_request:
|
|
||||||
branches:
|
|
||||||
- master
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
runs-on:
|
|
||||||
- linux
|
|
||||||
- ${{ matrix.arch }}
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
os:
|
|
||||||
- linux
|
|
||||||
arch:
|
|
||||||
- amd64
|
|
||||||
- arm64
|
|
||||||
- arm
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
container:
|
|
||||||
image: ghcr.io/catthehacker/ubuntu:act-24.04
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v6
|
|
||||||
with:
|
|
||||||
lfs: true
|
|
||||||
- name: Cache
|
|
||||||
id: pki-cache
|
|
||||||
uses: actions/cache@v4
|
|
||||||
with:
|
|
||||||
path: go-build
|
|
||||||
key: pki-${{ matrix.arch }}-${{ hashFiles('**/go.mod','**/go.sum') }}
|
|
||||||
- name: Build pki
|
|
||||||
uses: ./.github/actions/builder
|
|
||||||
with:
|
|
||||||
steps: |
|
|
||||||
mkdir $GOARCH artifacts
|
|
||||||
go generate cmd/pki/*.go
|
|
||||||
go build -mod=vendor -o $GOARCH/pki -v cmd/pki/*.go
|
|
||||||
env:
|
|
||||||
GOOS: linux
|
|
||||||
GOARCH: ${{ matrix.arch }}
|
|
||||||
GOCACHE: ${{ forgejo.workspace }}/go-build
|
|
||||||
VERSION: ${{ forgejo.ref_name }}
|
|
||||||
- name: Create release archive
|
|
||||||
if: startsWith(forgejo.ref, 'refs/tags/')
|
|
||||||
run: |
|
|
||||||
cd ${{ matrix.arch }}
|
|
||||||
tar -czvf ../artifacts/pki-${{ github.ref_name }}-${{ matrix.os }}-${{ matrix.arch }}.tar.gz pki
|
|
||||||
- name: Upload artifacts
|
|
||||||
uses: actions/upload-artifact@v3
|
|
||||||
if: startsWith(forgejo.ref, 'refs/tags/')
|
|
||||||
with:
|
|
||||||
name: pki-artifacts
|
|
||||||
path: "artifacts/*"
|
|
||||||
release:
|
|
||||||
runs-on:
|
|
||||||
- linux
|
|
||||||
needs: [build]
|
|
||||||
container:
|
|
||||||
image: ghcr.io/catthehacker/ubuntu:act-24.04
|
|
||||||
outputs:
|
|
||||||
upload_url: ${{ steps.create_release.outputs.upload_url }}
|
|
||||||
if: startsWith(forgejo.ref, 'refs/tags/')
|
|
||||||
steps:
|
|
||||||
- name: Download artifacts
|
|
||||||
uses: actions/download-artifact@v3
|
|
||||||
with:
|
|
||||||
name: pki-artifacts
|
|
||||||
path: artifacts
|
|
||||||
- name: Create checksum files
|
|
||||||
run: |
|
|
||||||
cd artifacts
|
|
||||||
sha256sum *.tar.gz > sha256sum.txt
|
|
||||||
sha512sum *.tar.gz > sha512sum.txt
|
|
||||||
- name: Create Release
|
|
||||||
if: startsWith(forgejo.ref, 'refs/tags/')
|
|
||||||
id: create_release
|
|
||||||
uses: actions/forgejo-release@v2.7.3
|
|
||||||
with:
|
|
||||||
direction: upload
|
|
||||||
release-dir: artifacts/
|
|
||||||
title: "pki ${{ forgejo.ref_name }}"
|
|
||||||
18
Makefile
Normal file
18
Makefile
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# pki Makefile
|
||||||
|
|
||||||
|
GOCMD=go
|
||||||
|
GOBUILDCMD=${GOCMD} build
|
||||||
|
GOOPTIONS=-mod=vendor -ldflags="-s -w"
|
||||||
|
|
||||||
|
RMCMD=rm
|
||||||
|
BINNAME=pki
|
||||||
|
|
||||||
|
SRCFILES=cmd/pki/*.go
|
||||||
|
|
||||||
|
all: build
|
||||||
|
|
||||||
|
build:
|
||||||
|
${GOBUILDCMD} ${GOOPTIONS} ${SRCFILES}
|
||||||
|
|
||||||
|
clean:
|
||||||
|
${RMCMD} -f ${BINNAME}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
# pki
|
# pki
|
||||||
|
[](https://drone.paulbsd.com/paulbsd/pki)
|
||||||
[](https://git.paulbsd.com/paulbsd/pki/actions)
|
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
|
|
@ -11,7 +10,7 @@ PKI is a centralized Letsencrypt database server and renewer for certificate man
|
||||||
### Build
|
### Build
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
go build cmd/pki/pki.go
|
make
|
||||||
```
|
```
|
||||||
|
|
||||||
### Sample config in pki.ini
|
### Sample config in pki.ini
|
||||||
|
|
@ -41,7 +40,7 @@ ovhck=
|
||||||
## License
|
## License
|
||||||
|
|
||||||
```text
|
```text
|
||||||
Copyright (c) 2020, 2021, 2022 PaulBSD
|
Copyright (c) 2020, 2021 PaulBSD
|
||||||
All rights reserved.
|
All rights reserved.
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
|
|
||||||
62
ci-build.sh
Executable file
62
ci-build.sh
Executable file
|
|
@ -0,0 +1,62 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
PROJECTNAME=pki
|
||||||
|
RELEASENAME=${PROJECTNAME}
|
||||||
|
VERSION="0"
|
||||||
|
|
||||||
|
GOOPTIONS="-mod=vendor"
|
||||||
|
SRCFILES=cmd/${PROJECTNAME}/*.go
|
||||||
|
|
||||||
|
build() {
|
||||||
|
echo "Begin of build"
|
||||||
|
if [[ ! -z $DRONE_TAG ]]
|
||||||
|
then
|
||||||
|
echo "Drone tag set, let's do a release"
|
||||||
|
VERSION=$DRONE_TAG
|
||||||
|
echo "${PROJECTNAME} ${VERSION}" > /build/VERSION
|
||||||
|
elif [[ ! -z $DRONE_TAG ]]
|
||||||
|
then
|
||||||
|
echo "Drone not set, let's only do a build"
|
||||||
|
VERSION=$DRONE_COMMIT
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -z $VERSION && ! -z $GOOS && ! -z $GOARCH ]]
|
||||||
|
then
|
||||||
|
echo "Let's set a release name"
|
||||||
|
RELEASENAME=${PROJECTNAME}-${VERSION}-${GOOS}-${GOARCH}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Building project"
|
||||||
|
go build -o ${PROJECTNAME} ${GOOPTIONS} ${SRCFILES}
|
||||||
|
|
||||||
|
if [[ ! -z $DRONE_TAG ]]
|
||||||
|
then
|
||||||
|
echo "Let's make archives"
|
||||||
|
mkdir -p /build
|
||||||
|
tar -czvf /build/${RELEASENAME}.tar.gz ${PROJECTNAME}
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Removing binary file"
|
||||||
|
rm ${PROJECTNAME}
|
||||||
|
|
||||||
|
echo "End of build"
|
||||||
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
|
rm -rf $RELEASEDIR
|
||||||
|
}
|
||||||
|
|
||||||
|
case $1 in
|
||||||
|
"build")
|
||||||
|
build
|
||||||
|
;;
|
||||||
|
"clean")
|
||||||
|
clean
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "No options choosen"
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
56
go.mod
56
go.mod
|
|
@ -1,43 +1,41 @@
|
||||||
module git.paulbsd.com/paulbsd/pki
|
module git.paulbsd.com/paulbsd/pki
|
||||||
|
|
||||||
go 1.25
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/go-acme/lego/v4 v4.29.0
|
github.com/go-acme/lego/v4 v4.4.0
|
||||||
github.com/golang/snappy v1.0.0 // indirect
|
github.com/golang/snappy v0.0.4 // indirect
|
||||||
github.com/labstack/echo/v4 v4.14.0
|
github.com/google/go-cmp v0.5.5 // indirect
|
||||||
github.com/lib/pq v1.10.9
|
github.com/gopherjs/gopherjs v0.0.0-20210406100015-1e088ea4ee04 // indirect
|
||||||
github.com/miekg/dns v1.1.69 // indirect
|
github.com/labstack/echo/v4 v4.5.0
|
||||||
|
github.com/lib/pq v1.10.3
|
||||||
|
github.com/miekg/dns v1.1.43 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.0 // indirect
|
github.com/onsi/ginkgo v1.16.0 // indirect
|
||||||
github.com/onsi/gomega v1.11.0 // indirect
|
github.com/onsi/gomega v1.11.0 // indirect
|
||||||
golang.org/x/crypto v0.46.0 // indirect
|
github.com/smartystreets/assertions v1.2.0 // indirect
|
||||||
golang.org/x/net v0.48.0 // indirect
|
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect
|
||||||
golang.org/x/sys v0.39.0 // indirect
|
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f // indirect
|
||||||
golang.org/x/text v0.32.0 // indirect
|
golang.org/x/sys v0.0.0-20210903071746-97244b99971b // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/text v0.3.7 // indirect
|
||||||
gopkg.in/ini.v1 v1.67.0
|
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac // indirect
|
||||||
xorm.io/builder v0.3.13 // indirect
|
gopkg.in/ini.v1 v1.62.1
|
||||||
xorm.io/xorm v1.3.11
|
xorm.io/builder v0.3.9 // indirect
|
||||||
|
xorm.io/xorm v1.2.3
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
github.com/goccy/go-json v0.7.8 // indirect
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
|
|
||||||
github.com/goccy/go-json v0.10.5 // indirect
|
|
||||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect
|
github.com/json-iterator/go v1.1.11 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/labstack/gommon v0.3.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
github.com/ovh/go-ovh v1.1.0 // indirect
|
||||||
github.com/syndtr/goleveldb v1.0.0 // indirect
|
github.com/syndtr/goleveldb v1.0.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.1 // indirect
|
||||||
golang.org/x/mod v0.31.0 // indirect
|
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
|
||||||
golang.org/x/oauth2 v0.34.0 // indirect
|
|
||||||
golang.org/x/sync v0.19.0 // indirect
|
|
||||||
golang.org/x/tools v0.40.0 // indirect
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,10 @@ package cert
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
func (e *Entry) Z() {
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry is the main struct for stored certificates
|
// Entry is the main struct for stored certificates
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
ID int `xorm:"pk autoincr"`
|
ID int `xorm:"pk autoincr"`
|
||||||
Domain string `xorm:"notnull"`
|
Domains string `xorm:"notnull"`
|
||||||
Certificate string `xorm:"text notnull"`
|
Certificate string `xorm:"text notnull"`
|
||||||
PrivateKey string `xorm:"text notnull"`
|
PrivateKey string `xorm:"text notnull"`
|
||||||
AuthURL string `xorm:"notnull"`
|
AuthURL string `xorm:"notnull"`
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/utils"
|
"git.paulbsd.com/paulbsd/pki/utils"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
|
|
@ -38,6 +39,7 @@ func (cfg *Config) GetConfig() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
pkisection := inicfg.Section("pki")
|
pkisection := inicfg.Section("pki")
|
||||||
|
options := make(map[string]string)
|
||||||
|
|
||||||
cfg.DbParams.DbHostname = pkisection.Key("db_hostname").MustString("localhost")
|
cfg.DbParams.DbHostname = pkisection.Key("db_hostname").MustString("localhost")
|
||||||
cfg.DbParams.DbName = pkisection.Key("db_name").MustString("database")
|
cfg.DbParams.DbName = pkisection.Key("db_name").MustString("database")
|
||||||
|
|
@ -53,13 +55,23 @@ func (cfg *Config) GetConfig() error {
|
||||||
cfg.ACME.Env = pkisection.Key("env").MustString("prod")
|
cfg.ACME.Env = pkisection.Key("env").MustString("prod")
|
||||||
cfg.ACME.MaxDaysBefore = pkisection.Key("maxdaysbefore").MustInt(0)
|
cfg.ACME.MaxDaysBefore = pkisection.Key("maxdaysbefore").MustInt(0)
|
||||||
|
|
||||||
|
options["ovhendpoint"] = pkisection.Key("ovhendpoint").MustString("ovh-eu")
|
||||||
|
options["ovhak"] = pkisection.Key("ovhak").MustString("")
|
||||||
|
options["ovhas"] = pkisection.Key("ovhas").MustString("")
|
||||||
|
options["ovhck"] = pkisection.Key("ovhck").MustString("")
|
||||||
|
|
||||||
|
cfg.ACME.ProviderOptions = options
|
||||||
|
for k, v := range options {
|
||||||
|
if v == "" {
|
||||||
|
utils.Advice(fmt.Sprintf("OVH provider parameter %s not set", k))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
switch cfg.ACME.Env {
|
switch cfg.ACME.Env {
|
||||||
case "prod":
|
case "prod":
|
||||||
cfg.ACME.AuthURL = lego.LEDirectoryProduction
|
cfg.ACME.AuthURL = lego.LEDirectoryProduction
|
||||||
case "staging":
|
case "staging":
|
||||||
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
||||||
default:
|
|
||||||
cfg.ACME.AuthURL = lego.LEDirectoryStaging
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -88,6 +100,7 @@ type Config struct {
|
||||||
ACME struct {
|
ACME struct {
|
||||||
Env string `json:"env"`
|
Env string `json:"env"`
|
||||||
AuthURL string `json:"authurl"`
|
AuthURL string `json:"authurl"`
|
||||||
|
ProviderOptions map[string]string `json:"provideroptions"`
|
||||||
MaxDaysBefore int `json:"maxdaysbefore"`
|
MaxDaysBefore int `json:"maxdaysbefore"`
|
||||||
}
|
}
|
||||||
Init struct {
|
Init struct {
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ import (
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/domain"
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||||
_ "github.com/lib/pq"
|
_ "github.com/lib/pq"
|
||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
|
|
@ -17,8 +16,8 @@ import (
|
||||||
// Init creates connection to database and exec Schema
|
// Init creates connection to database and exec Schema
|
||||||
func Init(cfg *config.Config) (err error) {
|
func Init(cfg *config.Config) (err error) {
|
||||||
var databaseEngine = "postgres"
|
var databaseEngine = "postgres"
|
||||||
tables := []any{cert.Entry{},
|
tables := []interface{}{cert.Entry{},
|
||||||
pki.User{}, domain.Domain{}, pki.Provider{}}
|
pki.User{}}
|
||||||
|
|
||||||
cfg.Db, err = xorm.NewEngine(databaseEngine,
|
cfg.Db, err = xorm.NewEngine(databaseEngine,
|
||||||
fmt.Sprintf("%s://%s:%s@%s/%s",
|
fmt.Sprintf("%s://%s:%s@%s/%s",
|
||||||
|
|
@ -42,14 +41,8 @@ func Init(cfg *config.Config) (err error) {
|
||||||
|
|
||||||
log.Println("Syncing tables")
|
log.Println("Syncing tables")
|
||||||
for _, table := range tables {
|
for _, table := range tables {
|
||||||
err = cfg.Db.CreateTables(table)
|
cfg.Db.CreateTables(table)
|
||||||
if err != nil {
|
cfg.Db.Sync2(table)
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
err = cfg.Db.Sync(table)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalln(err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
package domain
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// Domain describes a domain
|
|
||||||
type Domain struct {
|
|
||||||
ID int `xorm:"pk autoincr"`
|
|
||||||
Domain string `xorm:"text notnull unique(domain_provider)"`
|
|
||||||
Provider string `xorm:"text notnull unique(domain_provider)"`
|
|
||||||
Created time.Time `xorm:"created notnull"`
|
|
||||||
Updated time.Time `xorm:"updated notnull"`
|
|
||||||
}
|
|
||||||
|
|
@ -9,13 +9,12 @@ import (
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/domain"
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/certificate"
|
"github.com/go-acme/lego/v4/certificate"
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
)
|
)
|
||||||
|
|
@ -30,12 +29,15 @@ func (u *User) Init(cfg *config.Config) (err error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEntry returns requested acme ressource in database relative to domain
|
// GetEntry returns requested acme ressource in database relative to domain
|
||||||
func (u *User) GetEntry(cfg *config.Config, domain *string) (Entry cert.Entry, err error) {
|
func (u *User) GetEntry(cfg *config.Config, domains []string) (Entry cert.Entry, err error) {
|
||||||
has, err := cfg.Db.Where("domain = ?", domain).And(
|
|
||||||
|
has, err := cfg.Db.Where("domains = ?", strings.Join(domains, ",")).And(
|
||||||
"auth_url = ?", cfg.ACME.AuthURL).And(
|
"auth_url = ?", cfg.ACME.AuthURL).And(
|
||||||
fmt.Sprintf("validity_end::timestamp-'%d DAY'::INTERVAL >= now()", cfg.ACME.MaxDaysBefore)).Desc(
|
fmt.Sprintf("validity_end::timestamp-'%d DAY'::INTERVAL >= now()", cfg.ACME.MaxDaysBefore)).Desc(
|
||||||
"id").Get(&Entry)
|
"id").Get(&Entry)
|
||||||
|
|
||||||
|
fmt.Println(has, err)
|
||||||
|
|
||||||
if !has {
|
if !has {
|
||||||
err = fmt.Errorf("entry doesn't exists")
|
err = fmt.Errorf("entry doesn't exists")
|
||||||
}
|
}
|
||||||
|
|
@ -66,45 +68,12 @@ func (u *User) HandleRegistration(cfg *config.Config, client *lego.Client) (err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RequestNewCert returns a newly requested certificate to letsencrypt
|
// RequestNewCert returns a newly requested certificate to letsencrypt
|
||||||
func (u *User) RequestNewCert(cfg *config.Config, domainnames *[]string) (certs *certificate.Resource, err error) {
|
func (u *User) RequestNewCert(cfg *config.Config, domains []string) (certificates *certificate.Resource, err error) {
|
||||||
legoconfig := lego.NewConfig(u)
|
legoconfig := lego.NewConfig(u)
|
||||||
legoconfig.CADirURL = cfg.ACME.AuthURL
|
legoconfig.CADirURL = cfg.ACME.AuthURL
|
||||||
legoconfig.Certificate.KeyType = certcrypto.RSA2048
|
legoconfig.Certificate.KeyType = certcrypto.RSA2048
|
||||||
|
|
||||||
var dom domain.Domain
|
ovhprovider, err := initProvider(cfg)
|
||||||
var has bool
|
|
||||||
for _, d := range *domainnames {
|
|
||||||
dom = domain.Domain{Domain: d}
|
|
||||||
if has, err = cfg.Db.Get(&dom); has {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !has {
|
|
||||||
err = fmt.Errorf("supplied domain not in allowed domains")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var provider challenge.Provider
|
|
||||||
var pkiprovider Provider
|
|
||||||
|
|
||||||
_, err = cfg.Db.Where("name = ?", dom.Provider).Get(&pkiprovider)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch dom.Provider {
|
|
||||||
case "ovh":
|
|
||||||
provider, err = pkiprovider.initOVHProvider()
|
|
||||||
case "pdns":
|
|
||||||
provider, err = pkiprovider.initPowerDNSProvider()
|
|
||||||
default:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
@ -114,7 +83,7 @@ func (u *User) RequestNewCert(cfg *config.Config, domainnames *[]string) (certs
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = client.Challenge.SetDNS01Provider(provider)
|
err = client.Challenge.SetDNS01Provider(ovhprovider)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
@ -128,15 +97,14 @@ func (u *User) RequestNewCert(cfg *config.Config, domainnames *[]string) (certs
|
||||||
}
|
}
|
||||||
|
|
||||||
request := certificate.ObtainRequest{
|
request := certificate.ObtainRequest{
|
||||||
Domains: *domainnames,
|
Domains: domains,
|
||||||
Bundle: true,
|
Bundle: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
certs, err = client.Certificate.Obtain(request)
|
certificates, err = client.Certificate.Obtain(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,65 +1,20 @@
|
||||||
package pki
|
package pki
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
"log"
|
|
||||||
"net/url"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/pdns"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// initOVHProvider initialize DNS provider configuration
|
// initProvider initialize DNS provider configuration
|
||||||
func (p *Provider) initOVHProvider() (ovhprovider *ovh.DNSProvider, err error) {
|
func initProvider(cfg *config.Config) (ovhprovider *ovh.DNSProvider, err error) {
|
||||||
ovhconfig := ovh.NewDefaultConfig()
|
ovhconfig := ovh.NewDefaultConfig()
|
||||||
|
|
||||||
var data = make(map[string]string)
|
ovhconfig.APIEndpoint = cfg.ACME.ProviderOptions["ovhendpoint"]
|
||||||
err = json.Unmarshal([]byte(p.Config), &data)
|
ovhconfig.ApplicationKey = cfg.ACME.ProviderOptions["ovhak"]
|
||||||
if err != nil {
|
ovhconfig.ApplicationSecret = cfg.ACME.ProviderOptions["ovhas"]
|
||||||
log.Println(err)
|
ovhconfig.ConsumerKey = cfg.ACME.ProviderOptions["ovhck"]
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ovhconfig.APIEndpoint = data["ovhendpoint"]
|
|
||||||
ovhconfig.ApplicationKey = data["ovhak"]
|
|
||||||
ovhconfig.ApplicationSecret = data["ovhas"]
|
|
||||||
ovhconfig.ConsumerKey = data["ovhck"]
|
|
||||||
|
|
||||||
ovhprovider, err = ovh.NewDNSProviderConfig(ovhconfig)
|
ovhprovider, err = ovh.NewDNSProviderConfig(ovhconfig)
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// initPowerDNSProvider initialize DNS provider configuration
|
|
||||||
func (p *Provider) initPowerDNSProvider() (pdnsprovider *pdns.DNSProvider, err error) {
|
|
||||||
pdnsconfig := pdns.NewDefaultConfig()
|
|
||||||
|
|
||||||
var data = make(map[string]string)
|
|
||||||
err = json.Unmarshal([]byte(p.Config), &data)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
pdnsconfig.Host, err = url.Parse(data["pdnsapiurl"])
|
|
||||||
pdnsconfig.APIKey = data["pdnsapikey"]
|
|
||||||
|
|
||||||
pdnsprovider, err = pdns.NewDNSProviderConfig(pdnsconfig)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Provider struct {
|
|
||||||
ID int `xorm:"pk autoincr"`
|
|
||||||
Name string `xorm:"text notnull unique"`
|
|
||||||
Config string `xorm:"json notnull"`
|
|
||||||
Created time.Time `xorm:"created notnull"`
|
|
||||||
Updated time.Time `xorm:"updated notnull"`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/config"
|
"git.paulbsd.com/paulbsd/pki/src/config"
|
||||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||||
|
|
@ -29,18 +30,13 @@ func RunServer(cfg *config.Config) (err error) {
|
||||||
e.GET("/", func(c echo.Context) error {
|
e.GET("/", func(c echo.Context) error {
|
||||||
return c.String(http.StatusOK, "Welcome to PKI software (https://git.paulbsd.com/paulbsd/pki)")
|
return c.String(http.StatusOK, "Welcome to PKI software (https://git.paulbsd.com/paulbsd/pki)")
|
||||||
})
|
})
|
||||||
e.POST("/cert", func(c echo.Context) (err error) {
|
e.GET("/domain/:domains", func(c echo.Context) (err error) {
|
||||||
var request = new(EntryRequest)
|
var result EntryResponse
|
||||||
var result = make(map[string]EntryResponse)
|
var domains = strings.Split(c.Param("domains"), ",")
|
||||||
err = c.Bind(&request)
|
|
||||||
if err != nil {
|
|
||||||
log.Println(err)
|
|
||||||
return c.JSON(http.StatusInternalServerError, "error parsing request")
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("Providing %s to user %s at %s\n", request.Domains, c.Get("username"), c.RealIP())
|
log.Println(fmt.Sprintf("Providing %s to user %s at %s", domains, c.Get("username"), c.RealIP()))
|
||||||
|
|
||||||
result, err = GetCertificate(cfg, c.Get("user").(*pki.User), &request.Domains)
|
result, err = GetCertificate(cfg, c.Get("user").(*pki.User), domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.String(http.StatusInternalServerError, fmt.Sprintf("%s", err))
|
return c.String(http.StatusInternalServerError, fmt.Sprintf("%s", err))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.paulbsd.com/paulbsd/pki/src/cert"
|
"git.paulbsd.com/paulbsd/pki/src/cert"
|
||||||
|
|
@ -13,24 +14,18 @@ import (
|
||||||
"git.paulbsd.com/paulbsd/pki/src/pki"
|
"git.paulbsd.com/paulbsd/pki/src/pki"
|
||||||
)
|
)
|
||||||
|
|
||||||
const timeformatstring string = "2006-01-02 15:04:05"
|
|
||||||
|
|
||||||
var domainRegex, err = regexp.Compile(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`)
|
|
||||||
|
|
||||||
// GetCertificate get certificate from database if exists, of request it from ACME
|
// GetCertificate get certificate from database if exists, of request it from ACME
|
||||||
func GetCertificate(cfg *config.Config, user *pki.User, domains *[]string) (result map[string]EntryResponse, err error) {
|
func GetCertificate(cfg *config.Config, user *pki.User, domains []string) (result EntryResponse, err error) {
|
||||||
err = CheckDomains(domains)
|
err = CheckDomains(domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
result = make(map[string]EntryResponse)
|
|
||||||
|
|
||||||
firstdomain := (*domains)[0]
|
entry, err := user.GetEntry(cfg, domains)
|
||||||
entry, err := user.GetEntry(cfg, &firstdomain)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
certs, err := user.RequestNewCert(cfg, domains)
|
certs, err := user.RequestNewCert(cfg, domains)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error fetching new certificate %s\n", err)
|
log.Println(fmt.Sprintf("Error fetching new certificate %s", err))
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
NotBefore, NotAfter, err := GetDates(certs.Certificate)
|
NotBefore, NotAfter, err := GetDates(certs.Certificate)
|
||||||
|
|
@ -38,36 +33,33 @@ func GetCertificate(cfg *config.Config, user *pki.User, domains *[]string) (resu
|
||||||
log.Println("Error where parsing dates")
|
log.Println("Error where parsing dates")
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
entry := cert.Entry{Domain: certs.Domain,
|
entry := cert.Entry{Domains: strings.Join(domains, ","),
|
||||||
Certificate: string(certs.Certificate),
|
Certificate: string(certs.Certificate),
|
||||||
PrivateKey: string(certs.PrivateKey),
|
PrivateKey: string(certs.PrivateKey),
|
||||||
ValidityBegin: NotBefore,
|
ValidityBegin: NotBefore,
|
||||||
ValidityEnd: NotAfter,
|
ValidityEnd: NotAfter,
|
||||||
AuthURL: cfg.ACME.AuthURL}
|
AuthURL: cfg.ACME.AuthURL}
|
||||||
cfg.Db.Insert(&entry)
|
cfg.Db.Insert(&entry)
|
||||||
result[firstdomain] = convertEntryToResponse(entry)
|
result = convertEntryToResponse(entry)
|
||||||
return result, err
|
return result, err
|
||||||
}
|
}
|
||||||
result[firstdomain] = convertEntryToResponse(entry)
|
result = convertEntryToResponse(entry)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckDomains check if requested domains are valid
|
// CheckDomains check if requested domains are valid
|
||||||
func CheckDomains(domains *[]string) (err error) {
|
func CheckDomains(domains []string) (err error) {
|
||||||
for _, domain := range *domains {
|
domainRegex, err := regexp.Compile(`^[a-z0-9\*]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,6}$`)
|
||||||
err = CheckDomain(&domain)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CheckDomain check if requested domain are valid
|
for _, d := range domains {
|
||||||
func CheckDomain(domain *string) (err error) {
|
res := domainRegex.Match([]byte(d))
|
||||||
res := domainRegex.Match([]byte(*domain))
|
|
||||||
if !res {
|
if !res {
|
||||||
return fmt.Errorf("Domain %s has not a valid syntax %s, please verify", *domain, err)
|
return fmt.Errorf(fmt.Sprintf("Domain %s has not a valid syntax %s, please verify", d, err))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +80,9 @@ func GetDates(cert []byte) (NotBefore time.Time, NotAfter time.Time, err error)
|
||||||
|
|
||||||
// convertEntryToResponse converts database ACME entry to JSON ACME entry
|
// convertEntryToResponse converts database ACME entry to JSON ACME entry
|
||||||
func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
||||||
out.Domains = append(out.Domains, in.Domain)
|
timeformatstring := "2006-01-02 15:04:05"
|
||||||
|
|
||||||
|
out.Domains = in.Domains
|
||||||
out.Certificate = in.Certificate
|
out.Certificate = in.Certificate
|
||||||
out.PrivateKey = in.PrivateKey
|
out.PrivateKey = in.PrivateKey
|
||||||
out.ValidityBegin = in.ValidityBegin.Format(timeformatstring)
|
out.ValidityBegin = in.ValidityBegin.Format(timeformatstring)
|
||||||
|
|
@ -97,14 +91,9 @@ func convertEntryToResponse(in cert.Entry) (out EntryResponse) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// EntryRequest
|
|
||||||
type EntryRequest struct {
|
|
||||||
Domains []string `json:"domains"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// EntryResponse is the struct defining JSON response from webservice
|
// EntryResponse is the struct defining JSON response from webservice
|
||||||
type EntryResponse struct {
|
type EntryResponse struct {
|
||||||
Domains []string `json:"domains"`
|
Domains string `json:"domains"`
|
||||||
Certificate string `json:"certificate"`
|
Certificate string `json:"certificate"`
|
||||||
PrivateKey string `json:"privatekey"`
|
PrivateKey string `json:"privatekey"`
|
||||||
ValidityBegin string `json:"validitybegin"`
|
ValidityBegin string `json:"validitybegin"`
|
||||||
|
|
|
||||||
10
vendor/github.com/cenkalti/backoff/v4/.travis.yml
generated
vendored
Normal file
10
vendor/github.com/cenkalti/backoff/v4/.travis.yml
generated
vendored
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
language: go
|
||||||
|
go:
|
||||||
|
- 1.13
|
||||||
|
- 1.x
|
||||||
|
- tip
|
||||||
|
before_install:
|
||||||
|
- go get github.com/mattn/goveralls
|
||||||
|
- go get golang.org/x/tools/cmd/cover
|
||||||
|
script:
|
||||||
|
- $HOME/gopath/bin/goveralls -service=travis-ci
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
# Exponential Backoff [![GoDoc][godoc image]][godoc]
|
# Exponential Backoff [![GoDoc][godoc image]][godoc] [![Build Status][travis image]][travis] [![Coverage Status][coveralls image]][coveralls]
|
||||||
|
|
||||||
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
This is a Go port of the exponential backoff algorithm from [Google's HTTP Client Library for Java][google-http-java-client].
|
||||||
|
|
||||||
|
|
@ -9,11 +9,9 @@ The retries exponentially increase and stop increasing when a certain threshold
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Import path is `github.com/cenkalti/backoff/v5`. Please note the version part at the end.
|
Import path is `github.com/cenkalti/backoff/v4`. Please note the version part at the end.
|
||||||
|
|
||||||
For most cases, use `Retry` function. See [example_test.go][example] for an example.
|
Use https://pkg.go.dev/github.com/cenkalti/backoff/v4 to view the documentation.
|
||||||
|
|
||||||
If you have specific needs, copy `Retry` function (from [retry.go][retry-src]) into your code and modify it as needed.
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
|
|
@ -21,11 +19,14 @@ If you have specific needs, copy `Retry` function (from [retry.go][retry-src]) i
|
||||||
* Please don't send a PR without opening an issue and discussing it first.
|
* Please don't send a PR without opening an issue and discussing it first.
|
||||||
* If proposed change is not a common use case, I will probably not accept it.
|
* If proposed change is not a common use case, I will probably not accept it.
|
||||||
|
|
||||||
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v5
|
[godoc]: https://pkg.go.dev/github.com/cenkalti/backoff/v4
|
||||||
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
[godoc image]: https://godoc.org/github.com/cenkalti/backoff?status.png
|
||||||
|
[travis]: https://travis-ci.org/cenkalti/backoff
|
||||||
|
[travis image]: https://travis-ci.org/cenkalti/backoff.png?branch=master
|
||||||
|
[coveralls]: https://coveralls.io/github/cenkalti/backoff?branch=master
|
||||||
|
[coveralls image]: https://coveralls.io/repos/github/cenkalti/backoff/badge.svg?branch=master
|
||||||
|
|
||||||
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
|
[google-http-java-client]: https://github.com/google/google-http-java-client/blob/da1aa993e90285ec18579f1553339b00e19b3ab5/google-http-client/src/main/java/com/google/api/client/util/ExponentialBackOff.java
|
||||||
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
[exponential backoff wiki]: http://en.wikipedia.org/wiki/Exponential_backoff
|
||||||
|
|
||||||
[retry-src]: https://github.com/cenkalti/backoff/blob/v5/retry.go
|
[advanced example]: https://pkg.go.dev/github.com/cenkalti/backoff/v4?tab=doc#pkg-examples
|
||||||
[example]: https://github.com/cenkalti/backoff/blob/v5/example_test.go
|
|
||||||
|
|
@ -15,12 +15,12 @@ import "time"
|
||||||
// BackOff is a backoff policy for retrying an operation.
|
// BackOff is a backoff policy for retrying an operation.
|
||||||
type BackOff interface {
|
type BackOff interface {
|
||||||
// NextBackOff returns the duration to wait before retrying the operation,
|
// NextBackOff returns the duration to wait before retrying the operation,
|
||||||
// backoff.Stop to indicate that no more retries should be made.
|
// or backoff. Stop to indicate that no more retries should be made.
|
||||||
//
|
//
|
||||||
// Example usage:
|
// Example usage:
|
||||||
//
|
//
|
||||||
// duration := backoff.NextBackOff()
|
// duration := backoff.NextBackOff();
|
||||||
// if duration == backoff.Stop {
|
// if (duration == backoff.Stop) {
|
||||||
// // Do not retry operation.
|
// // Do not retry operation.
|
||||||
// } else {
|
// } else {
|
||||||
// // Sleep for duration and retry operation.
|
// // Sleep for duration and retry operation.
|
||||||
62
vendor/github.com/cenkalti/backoff/v4/context.go
generated
vendored
Normal file
62
vendor/github.com/cenkalti/backoff/v4/context.go
generated
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BackOffContext is a backoff policy that stops retrying after the context
|
||||||
|
// is canceled.
|
||||||
|
type BackOffContext interface { // nolint: golint
|
||||||
|
BackOff
|
||||||
|
Context() context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
type backOffContext struct {
|
||||||
|
BackOff
|
||||||
|
ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithContext returns a BackOffContext with context ctx
|
||||||
|
//
|
||||||
|
// ctx must not be nil
|
||||||
|
func WithContext(b BackOff, ctx context.Context) BackOffContext { // nolint: golint
|
||||||
|
if ctx == nil {
|
||||||
|
panic("nil context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b, ok := b.(*backOffContext); ok {
|
||||||
|
return &backOffContext{
|
||||||
|
BackOff: b.BackOff,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &backOffContext{
|
||||||
|
BackOff: b,
|
||||||
|
ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getContext(b BackOff) context.Context {
|
||||||
|
if cb, ok := b.(BackOffContext); ok {
|
||||||
|
return cb.Context()
|
||||||
|
}
|
||||||
|
if tb, ok := b.(*backOffTries); ok {
|
||||||
|
return getContext(tb.delegate)
|
||||||
|
}
|
||||||
|
return context.Background()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backOffContext) Context() context.Context {
|
||||||
|
return b.ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backOffContext) NextBackOff() time.Duration {
|
||||||
|
select {
|
||||||
|
case <-b.ctx.Done():
|
||||||
|
return Stop
|
||||||
|
default:
|
||||||
|
return b.BackOff.NextBackOff()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package backoff
|
package backoff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand/v2"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -28,7 +28,13 @@ multiplied by the exponential, that is, between 2 and 6 seconds.
|
||||||
|
|
||||||
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
Note: MaxInterval caps the RetryInterval and not the randomized interval.
|
||||||
|
|
||||||
Example: Given the following default arguments, for 9 tries the sequence will be:
|
If the time elapsed since an ExponentialBackOff instance is created goes past the
|
||||||
|
MaxElapsedTime, then the method NextBackOff() starts returning backoff.Stop.
|
||||||
|
|
||||||
|
The elapsed time can be reset by calling Reset().
|
||||||
|
|
||||||
|
Example: Given the following default arguments, for 10 tries the sequence will be,
|
||||||
|
and assuming we go over the MaxElapsedTime on the 10th try:
|
||||||
|
|
||||||
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
Request # RetryInterval (seconds) Randomized Interval (seconds)
|
||||||
|
|
||||||
|
|
@ -41,6 +47,7 @@ Example: Given the following default arguments, for 9 tries the sequence will be
|
||||||
7 5.692 [2.846, 8.538]
|
7 5.692 [2.846, 8.538]
|
||||||
8 8.538 [4.269, 12.807]
|
8 8.538 [4.269, 12.807]
|
||||||
9 12.807 [6.403, 19.210]
|
9 12.807 [6.403, 19.210]
|
||||||
|
10 19.210 backoff.Stop
|
||||||
|
|
||||||
Note: Implementation is not thread-safe.
|
Note: Implementation is not thread-safe.
|
||||||
*/
|
*/
|
||||||
|
|
@ -49,8 +56,19 @@ type ExponentialBackOff struct {
|
||||||
RandomizationFactor float64
|
RandomizationFactor float64
|
||||||
Multiplier float64
|
Multiplier float64
|
||||||
MaxInterval time.Duration
|
MaxInterval time.Duration
|
||||||
|
// After MaxElapsedTime the ExponentialBackOff returns Stop.
|
||||||
|
// It never stops if MaxElapsedTime == 0.
|
||||||
|
MaxElapsedTime time.Duration
|
||||||
|
Stop time.Duration
|
||||||
|
Clock Clock
|
||||||
|
|
||||||
currentInterval time.Duration
|
currentInterval time.Duration
|
||||||
|
startTime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clock is an interface that returns current time for BackOff.
|
||||||
|
type Clock interface {
|
||||||
|
Now() time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// Default values for ExponentialBackOff.
|
// Default values for ExponentialBackOff.
|
||||||
|
|
@ -59,37 +77,63 @@ const (
|
||||||
DefaultRandomizationFactor = 0.5
|
DefaultRandomizationFactor = 0.5
|
||||||
DefaultMultiplier = 1.5
|
DefaultMultiplier = 1.5
|
||||||
DefaultMaxInterval = 60 * time.Second
|
DefaultMaxInterval = 60 * time.Second
|
||||||
|
DefaultMaxElapsedTime = 15 * time.Minute
|
||||||
)
|
)
|
||||||
|
|
||||||
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
// NewExponentialBackOff creates an instance of ExponentialBackOff using default values.
|
||||||
func NewExponentialBackOff() *ExponentialBackOff {
|
func NewExponentialBackOff() *ExponentialBackOff {
|
||||||
return &ExponentialBackOff{
|
b := &ExponentialBackOff{
|
||||||
InitialInterval: DefaultInitialInterval,
|
InitialInterval: DefaultInitialInterval,
|
||||||
RandomizationFactor: DefaultRandomizationFactor,
|
RandomizationFactor: DefaultRandomizationFactor,
|
||||||
Multiplier: DefaultMultiplier,
|
Multiplier: DefaultMultiplier,
|
||||||
MaxInterval: DefaultMaxInterval,
|
MaxInterval: DefaultMaxInterval,
|
||||||
|
MaxElapsedTime: DefaultMaxElapsedTime,
|
||||||
|
Stop: Stop,
|
||||||
|
Clock: SystemClock,
|
||||||
}
|
}
|
||||||
|
b.Reset()
|
||||||
|
return b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type systemClock struct{}
|
||||||
|
|
||||||
|
func (t systemClock) Now() time.Time {
|
||||||
|
return time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SystemClock implements Clock interface that uses time.Now().
|
||||||
|
var SystemClock = systemClock{}
|
||||||
|
|
||||||
// Reset the interval back to the initial retry interval and restarts the timer.
|
// Reset the interval back to the initial retry interval and restarts the timer.
|
||||||
// Reset must be called before using b.
|
// Reset must be called before using b.
|
||||||
func (b *ExponentialBackOff) Reset() {
|
func (b *ExponentialBackOff) Reset() {
|
||||||
b.currentInterval = b.InitialInterval
|
b.currentInterval = b.InitialInterval
|
||||||
|
b.startTime = b.Clock.Now()
|
||||||
}
|
}
|
||||||
|
|
||||||
// NextBackOff calculates the next backoff interval using the formula:
|
// NextBackOff calculates the next backoff interval using the formula:
|
||||||
//
|
|
||||||
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
|
||||||
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
func (b *ExponentialBackOff) NextBackOff() time.Duration {
|
||||||
if b.currentInterval == 0 {
|
// Make sure we have not gone over the maximum elapsed time.
|
||||||
b.currentInterval = b.InitialInterval
|
elapsed := b.GetElapsedTime()
|
||||||
}
|
|
||||||
|
|
||||||
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
next := getRandomValueFromInterval(b.RandomizationFactor, rand.Float64(), b.currentInterval)
|
||||||
b.incrementCurrentInterval()
|
b.incrementCurrentInterval()
|
||||||
|
if b.MaxElapsedTime != 0 && elapsed+next > b.MaxElapsedTime {
|
||||||
|
return b.Stop
|
||||||
|
}
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetElapsedTime returns the elapsed time since an ExponentialBackOff instance
|
||||||
|
// is created and is reset when Reset() is called.
|
||||||
|
//
|
||||||
|
// The elapsed time is computed using time.Now().UnixNano(). It is
|
||||||
|
// safe to call even while the backoff policy is used by a running
|
||||||
|
// ticker.
|
||||||
|
func (b *ExponentialBackOff) GetElapsedTime() time.Duration {
|
||||||
|
return b.Clock.Now().Sub(b.startTime)
|
||||||
|
}
|
||||||
|
|
||||||
// Increments the current interval by multiplying it with the multiplier.
|
// Increments the current interval by multiplying it with the multiplier.
|
||||||
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||||
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
// Check for overflow, if overflow is detected set the current interval to the max interval.
|
||||||
|
|
@ -101,12 +145,8 @@ func (b *ExponentialBackOff) incrementCurrentInterval() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a random value from the following interval:
|
// Returns a random value from the following interval:
|
||||||
//
|
|
||||||
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
|
||||||
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
|
||||||
if randomizationFactor == 0 {
|
|
||||||
return currentInterval // make sure no randomness is used when randomizationFactor is 0.
|
|
||||||
}
|
|
||||||
var delta = randomizationFactor * float64(currentInterval)
|
var delta = randomizationFactor * float64(currentInterval)
|
||||||
var minInterval = float64(currentInterval) - delta
|
var minInterval = float64(currentInterval) - delta
|
||||||
var maxInterval = float64(currentInterval) + delta
|
var maxInterval = float64(currentInterval) + delta
|
||||||
112
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
Normal file
112
vendor/github.com/cenkalti/backoff/v4/retry.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// An Operation is executing by Retry() or RetryNotify().
|
||||||
|
// The operation will be retried using a backoff policy if it returns an error.
|
||||||
|
type Operation func() error
|
||||||
|
|
||||||
|
// Notify is a notify-on-error function. It receives an operation error and
|
||||||
|
// backoff delay if the operation failed (with an error).
|
||||||
|
//
|
||||||
|
// NOTE that if the backoff policy stated to stop retrying,
|
||||||
|
// the notify function isn't called.
|
||||||
|
type Notify func(error, time.Duration)
|
||||||
|
|
||||||
|
// Retry the operation o until it does not return error or BackOff stops.
|
||||||
|
// o is guaranteed to be run at least once.
|
||||||
|
//
|
||||||
|
// If o returns a *PermanentError, the operation is not retried, and the
|
||||||
|
// wrapped error is returned.
|
||||||
|
//
|
||||||
|
// Retry sleeps the goroutine for the duration returned by BackOff after a
|
||||||
|
// failed operation returns.
|
||||||
|
func Retry(o Operation, b BackOff) error {
|
||||||
|
return RetryNotify(o, b, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryNotify calls notify function with the error and wait duration
|
||||||
|
// for each failed attempt before sleep.
|
||||||
|
func RetryNotify(operation Operation, b BackOff, notify Notify) error {
|
||||||
|
return RetryNotifyWithTimer(operation, b, notify, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryNotifyWithTimer calls notify function with the error and wait duration using the given Timer
|
||||||
|
// for each failed attempt before sleep.
|
||||||
|
// A default timer that uses system timer is used when nil is passed.
|
||||||
|
func RetryNotifyWithTimer(operation Operation, b BackOff, notify Notify, t Timer) error {
|
||||||
|
var err error
|
||||||
|
var next time.Duration
|
||||||
|
if t == nil {
|
||||||
|
t = &defaultTimer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
t.Stop()
|
||||||
|
}()
|
||||||
|
|
||||||
|
ctx := getContext(b)
|
||||||
|
|
||||||
|
b.Reset()
|
||||||
|
for {
|
||||||
|
if err = operation(); err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var permanent *PermanentError
|
||||||
|
if errors.As(err, &permanent) {
|
||||||
|
return permanent.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
if next = b.NextBackOff(); next == Stop {
|
||||||
|
if cerr := ctx.Err(); cerr != nil {
|
||||||
|
return cerr
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if notify != nil {
|
||||||
|
notify(err, next)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Start(next)
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case <-t.C():
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PermanentError signals that the operation should not be retried.
|
||||||
|
type PermanentError struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PermanentError) Error() string {
|
||||||
|
return e.Err.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PermanentError) Unwrap() error {
|
||||||
|
return e.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *PermanentError) Is(target error) bool {
|
||||||
|
_, ok := target.(*PermanentError)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permanent wraps the given err in a *PermanentError.
|
||||||
|
func Permanent(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &PermanentError{
|
||||||
|
Err: err,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package backoff
|
package backoff
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
@ -13,7 +14,8 @@ type Ticker struct {
|
||||||
C <-chan time.Time
|
C <-chan time.Time
|
||||||
c chan time.Time
|
c chan time.Time
|
||||||
b BackOff
|
b BackOff
|
||||||
timer timer
|
ctx context.Context
|
||||||
|
timer Timer
|
||||||
stop chan struct{}
|
stop chan struct{}
|
||||||
stopOnce sync.Once
|
stopOnce sync.Once
|
||||||
}
|
}
|
||||||
|
|
@ -25,12 +27,22 @@ type Ticker struct {
|
||||||
// provided backoff policy (notably calling NextBackOff or Reset)
|
// provided backoff policy (notably calling NextBackOff or Reset)
|
||||||
// while the ticker is running.
|
// while the ticker is running.
|
||||||
func NewTicker(b BackOff) *Ticker {
|
func NewTicker(b BackOff) *Ticker {
|
||||||
|
return NewTickerWithTimer(b, &defaultTimer{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewTickerWithTimer returns a new Ticker with a custom timer.
|
||||||
|
// A default timer that uses system timer is used when nil is passed.
|
||||||
|
func NewTickerWithTimer(b BackOff, timer Timer) *Ticker {
|
||||||
|
if timer == nil {
|
||||||
|
timer = &defaultTimer{}
|
||||||
|
}
|
||||||
c := make(chan time.Time)
|
c := make(chan time.Time)
|
||||||
t := &Ticker{
|
t := &Ticker{
|
||||||
C: c,
|
C: c,
|
||||||
c: c,
|
c: c,
|
||||||
b: b,
|
b: b,
|
||||||
timer: &defaultTimer{},
|
ctx: getContext(b),
|
||||||
|
timer: timer,
|
||||||
stop: make(chan struct{}),
|
stop: make(chan struct{}),
|
||||||
}
|
}
|
||||||
t.b.Reset()
|
t.b.Reset()
|
||||||
|
|
@ -61,6 +73,8 @@ func (t *Ticker) run() {
|
||||||
case <-t.stop:
|
case <-t.stop:
|
||||||
t.c = nil // Prevent future ticks from being sent to the channel.
|
t.c = nil // Prevent future ticks from being sent to the channel.
|
||||||
return
|
return
|
||||||
|
case <-t.ctx.Done():
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ package backoff
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
type timer interface {
|
type Timer interface {
|
||||||
Start(duration time.Duration)
|
Start(duration time.Duration)
|
||||||
Stop()
|
Stop()
|
||||||
C() <-chan time.Time
|
C() <-chan time.Time
|
||||||
38
vendor/github.com/cenkalti/backoff/v4/tries.go
generated
vendored
Normal file
38
vendor/github.com/cenkalti/backoff/v4/tries.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
||||||
|
package backoff
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
/*
|
||||||
|
WithMaxRetries creates a wrapper around another BackOff, which will
|
||||||
|
return Stop if NextBackOff() has been called too many times since
|
||||||
|
the last time Reset() was called
|
||||||
|
|
||||||
|
Note: Implementation is not thread-safe.
|
||||||
|
*/
|
||||||
|
func WithMaxRetries(b BackOff, max uint64) BackOff {
|
||||||
|
return &backOffTries{delegate: b, maxTries: max}
|
||||||
|
}
|
||||||
|
|
||||||
|
type backOffTries struct {
|
||||||
|
delegate BackOff
|
||||||
|
maxTries uint64
|
||||||
|
numTries uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backOffTries) NextBackOff() time.Duration {
|
||||||
|
if b.maxTries == 0 {
|
||||||
|
return Stop
|
||||||
|
}
|
||||||
|
if b.maxTries > 0 {
|
||||||
|
if b.maxTries <= b.numTries {
|
||||||
|
return Stop
|
||||||
|
}
|
||||||
|
b.numTries++
|
||||||
|
}
|
||||||
|
return b.delegate.NextBackOff()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *backOffTries) Reset() {
|
||||||
|
b.numTries = 0
|
||||||
|
b.delegate.Reset()
|
||||||
|
}
|
||||||
29
vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md
generated
vendored
29
vendor/github.com/cenkalti/backoff/v5/CHANGELOG.md
generated
vendored
|
|
@ -1,29 +0,0 @@
|
||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [5.0.0] - 2024-12-19
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- RetryAfterError can be returned from an operation to indicate how long to wait before the next retry.
|
|
||||||
|
|
||||||
### Changed
|
|
||||||
|
|
||||||
- Retry function now accepts additional options for specifying max number of tries and max elapsed time.
|
|
||||||
- Retry function now accepts a context.Context.
|
|
||||||
- Operation function signature changed to return result (any type) and error.
|
|
||||||
|
|
||||||
### Removed
|
|
||||||
|
|
||||||
- RetryNotify* and RetryWithData functions. Only single Retry function remains.
|
|
||||||
- Optional arguments from ExponentialBackoff constructor.
|
|
||||||
- Clock and Timer interfaces.
|
|
||||||
|
|
||||||
### Fixed
|
|
||||||
|
|
||||||
- The original error is returned from Retry if there's a PermanentError. (#144)
|
|
||||||
- The Retry function respects the wrapped PermanentError. (#140)
|
|
||||||
46
vendor/github.com/cenkalti/backoff/v5/error.go
generated
vendored
46
vendor/github.com/cenkalti/backoff/v5/error.go
generated
vendored
|
|
@ -1,46 +0,0 @@
|
||||||
package backoff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// PermanentError signals that the operation should not be retried.
|
|
||||||
type PermanentError struct {
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// Permanent wraps the given err in a *PermanentError.
|
|
||||||
func Permanent(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &PermanentError{
|
|
||||||
Err: err,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns a string representation of the Permanent error.
|
|
||||||
func (e *PermanentError) Error() string {
|
|
||||||
return e.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwrap returns the wrapped error.
|
|
||||||
func (e *PermanentError) Unwrap() error {
|
|
||||||
return e.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetryAfterError signals that the operation should be retried after the given duration.
|
|
||||||
type RetryAfterError struct {
|
|
||||||
Duration time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// RetryAfter returns a RetryAfter error that specifies how long to wait before retrying.
|
|
||||||
func RetryAfter(seconds int) error {
|
|
||||||
return &RetryAfterError{Duration: time.Duration(seconds) * time.Second}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Error returns a string representation of the RetryAfter error.
|
|
||||||
func (e *RetryAfterError) Error() string {
|
|
||||||
return fmt.Sprintf("retry after %s", e.Duration)
|
|
||||||
}
|
|
||||||
139
vendor/github.com/cenkalti/backoff/v5/retry.go
generated
vendored
139
vendor/github.com/cenkalti/backoff/v5/retry.go
generated
vendored
|
|
@ -1,139 +0,0 @@
|
||||||
package backoff
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// DefaultMaxElapsedTime sets a default limit for the total retry duration.
|
|
||||||
const DefaultMaxElapsedTime = 15 * time.Minute
|
|
||||||
|
|
||||||
// Operation is a function that attempts an operation and may be retried.
|
|
||||||
type Operation[T any] func() (T, error)
|
|
||||||
|
|
||||||
// Notify is a function called on operation error with the error and backoff duration.
|
|
||||||
type Notify func(error, time.Duration)
|
|
||||||
|
|
||||||
// retryOptions holds configuration settings for the retry mechanism.
|
|
||||||
type retryOptions struct {
|
|
||||||
BackOff BackOff // Strategy for calculating backoff periods.
|
|
||||||
Timer timer // Timer to manage retry delays.
|
|
||||||
Notify Notify // Optional function to notify on each retry error.
|
|
||||||
MaxTries uint // Maximum number of retry attempts.
|
|
||||||
MaxElapsedTime time.Duration // Maximum total time for all retries.
|
|
||||||
}
|
|
||||||
|
|
||||||
type RetryOption func(*retryOptions)
|
|
||||||
|
|
||||||
// WithBackOff configures a custom backoff strategy.
|
|
||||||
func WithBackOff(b BackOff) RetryOption {
|
|
||||||
return func(args *retryOptions) {
|
|
||||||
args.BackOff = b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// withTimer sets a custom timer for managing delays between retries.
|
|
||||||
func withTimer(t timer) RetryOption {
|
|
||||||
return func(args *retryOptions) {
|
|
||||||
args.Timer = t
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithNotify sets a notification function to handle retry errors.
|
|
||||||
func WithNotify(n Notify) RetryOption {
|
|
||||||
return func(args *retryOptions) {
|
|
||||||
args.Notify = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxTries limits the number of all attempts.
|
|
||||||
func WithMaxTries(n uint) RetryOption {
|
|
||||||
return func(args *retryOptions) {
|
|
||||||
args.MaxTries = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// WithMaxElapsedTime limits the total duration for retry attempts.
|
|
||||||
func WithMaxElapsedTime(d time.Duration) RetryOption {
|
|
||||||
return func(args *retryOptions) {
|
|
||||||
args.MaxElapsedTime = d
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retry attempts the operation until success, a permanent error, or backoff completion.
|
|
||||||
// It ensures the operation is executed at least once.
|
|
||||||
//
|
|
||||||
// Returns the operation result or error if retries are exhausted or context is cancelled.
|
|
||||||
func Retry[T any](ctx context.Context, operation Operation[T], opts ...RetryOption) (T, error) {
|
|
||||||
// Initialize default retry options.
|
|
||||||
args := &retryOptions{
|
|
||||||
BackOff: NewExponentialBackOff(),
|
|
||||||
Timer: &defaultTimer{},
|
|
||||||
MaxElapsedTime: DefaultMaxElapsedTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply user-provided options to the default settings.
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(args)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer args.Timer.Stop()
|
|
||||||
|
|
||||||
startedAt := time.Now()
|
|
||||||
args.BackOff.Reset()
|
|
||||||
for numTries := uint(1); ; numTries++ {
|
|
||||||
// Execute the operation.
|
|
||||||
res, err := operation()
|
|
||||||
if err == nil {
|
|
||||||
return res, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop retrying if maximum tries exceeded.
|
|
||||||
if args.MaxTries > 0 && numTries >= args.MaxTries {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle permanent errors without retrying.
|
|
||||||
var permanent *PermanentError
|
|
||||||
if errors.As(err, &permanent) {
|
|
||||||
return res, permanent.Unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop retrying if context is cancelled.
|
|
||||||
if cerr := context.Cause(ctx); cerr != nil {
|
|
||||||
return res, cerr
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate next backoff duration.
|
|
||||||
next := args.BackOff.NextBackOff()
|
|
||||||
if next == Stop {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset backoff if RetryAfterError is encountered.
|
|
||||||
var retryAfter *RetryAfterError
|
|
||||||
if errors.As(err, &retryAfter) {
|
|
||||||
next = retryAfter.Duration
|
|
||||||
args.BackOff.Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stop retrying if maximum elapsed time exceeded.
|
|
||||||
if args.MaxElapsedTime > 0 && time.Since(startedAt)+next > args.MaxElapsedTime {
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Notify on error if a notifier function is provided.
|
|
||||||
if args.Notify != nil {
|
|
||||||
args.Notify(err, next)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait for the next backoff period or context cancellation.
|
|
||||||
args.Timer.Start(next)
|
|
||||||
select {
|
|
||||||
case <-args.Timer.C():
|
|
||||||
case <-ctx.Done():
|
|
||||||
return res, context.Cause(ctx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
vendor/github.com/go-acme/lego/v4/LICENSE
generated
vendored
1
vendor/github.com/go-acme/lego/v4/LICENSE
generated
vendored
|
|
@ -1,6 +1,5 @@
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017-2024 Ludovic Fernandez
|
|
||||||
Copyright (c) 2015-2017 Sebastian Erhart
|
Copyright (c) 2015-2017 Sebastian Erhart
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
|
|
||||||
25
vendor/github.com/go-acme/lego/v4/acme/api/account.go
generated
vendored
25
vendor/github.com/go-acme/lego/v4/acme/api/account.go
generated
vendored
|
|
@ -13,11 +13,10 @@ type AccountService service
|
||||||
// New Creates a new account.
|
// New Creates a new account.
|
||||||
func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
||||||
var account acme.Account
|
var account acme.Account
|
||||||
|
|
||||||
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
resp, err := a.core.post(a.core.GetDirectory().NewAccountURL, req, &account)
|
||||||
location := getLocation(resp)
|
location := getLocation(resp)
|
||||||
|
|
||||||
if location != "" {
|
if len(location) > 0 {
|
||||||
a.core.jws.SetKid(location)
|
a.core.jws.SetKid(location)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -30,9 +29,9 @@ func (a *AccountService) New(req acme.Account) (acme.ExtendedAccount, error) {
|
||||||
|
|
||||||
// NewEAB Creates a new account with an External Account Binding.
|
// NewEAB Creates a new account with an External Account Binding.
|
||||||
func (a *AccountService) NewEAB(accMsg acme.Account, kid, hmacEncoded string) (acme.ExtendedAccount, error) {
|
func (a *AccountService) NewEAB(accMsg acme.Account, kid, hmacEncoded string) (acme.ExtendedAccount, error) {
|
||||||
hmac, err := decodeEABHmac(hmacEncoded)
|
hmac, err := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.ExtendedAccount{}, err
|
return acme.ExtendedAccount{}, fmt.Errorf("acme: could not decode hmac key: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
|
eabJWS, err := a.core.signEABContent(a.core.GetDirectory().NewAccountURL, kid, hmac)
|
||||||
|
|
@ -52,12 +51,10 @@ func (a *AccountService) Get(accountURL string) (acme.Account, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var account acme.Account
|
var account acme.Account
|
||||||
|
|
||||||
_, err := a.core.postAsGet(accountURL, &account)
|
_, err := a.core.postAsGet(accountURL, &account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.Account{}, err
|
return acme.Account{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return account, nil
|
return account, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -68,7 +65,6 @@ func (a *AccountService) Update(accountURL string, req acme.Account) (acme.Accou
|
||||||
}
|
}
|
||||||
|
|
||||||
var account acme.Account
|
var account acme.Account
|
||||||
|
|
||||||
_, err := a.core.post(accountURL, req, &account)
|
_, err := a.core.post(accountURL, req, &account)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.Account{}, err
|
return acme.Account{}, err
|
||||||
|
|
@ -85,20 +81,5 @@ func (a *AccountService) Deactivate(accountURL string) error {
|
||||||
|
|
||||||
req := acme.Account{Status: acme.StatusDeactivated}
|
req := acme.Account{Status: acme.StatusDeactivated}
|
||||||
_, err := a.core.post(accountURL, req, nil)
|
_, err := a.core.post(accountURL, req, nil)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeEABHmac(hmacEncoded string) ([]byte, error) {
|
|
||||||
hmac, errRaw := base64.RawURLEncoding.DecodeString(hmacEncoded)
|
|
||||||
if errRaw == nil {
|
|
||||||
return hmac, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
hmac, err := base64.URLEncoding.DecodeString(hmacEncoded)
|
|
||||||
if err == nil {
|
|
||||||
return hmac, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, fmt.Errorf("acme: could not decode hmac key: %w", errors.Join(errRaw, err))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
43
vendor/github.com/go-acme/lego/v4/acme/api/api.go
generated
vendored
43
vendor/github.com/go-acme/lego/v4/acme/api/api.go
generated
vendored
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v5"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
||||||
"github.com/go-acme/lego/v4/acme/api/internal/secure"
|
"github.com/go-acme/lego/v4/acme/api/internal/secure"
|
||||||
|
|
@ -61,7 +61,7 @@ func New(httpClient *http.Client, userAgent, caDirURL, kid string, privateKey cr
|
||||||
|
|
||||||
// post performs an HTTP POST request and parses the response body as JSON,
|
// post performs an HTTP POST request and parses the response body as JSON,
|
||||||
// into the provided respBody object.
|
// into the provided respBody object.
|
||||||
func (a *Core) post(uri string, reqBody, response any) (*http.Response, error) {
|
func (a *Core) post(uri string, reqBody, response interface{}) (*http.Response, error) {
|
||||||
content, err := json.Marshal(reqBody)
|
content, err := json.Marshal(reqBody)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.New("failed to marshal message")
|
return nil, errors.New("failed to marshal message")
|
||||||
|
|
@ -71,51 +71,57 @@ func (a *Core) post(uri string, reqBody, response any) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
// postAsGet performs an HTTP POST ("POST-as-GET") request.
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-6.3
|
// https://tools.ietf.org/html/rfc8555#section-6.3
|
||||||
func (a *Core) postAsGet(uri string, response any) (*http.Response, error) {
|
func (a *Core) postAsGet(uri string, response interface{}) (*http.Response, error) {
|
||||||
return a.retrievablePost(uri, []byte{}, response)
|
return a.retrievablePost(uri, []byte{}, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Core) retrievablePost(uri string, content []byte, response any) (*http.Response, error) {
|
func (a *Core) retrievablePost(uri string, content []byte, response interface{}) (*http.Response, error) {
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
// during tests, allow to support ~90% of bad nonce with a minimum of attempts.
|
// during tests, allow to support ~90% of bad nonce with a minimum of attempts.
|
||||||
bo := backoff.NewExponentialBackOff()
|
bo := backoff.NewExponentialBackOff()
|
||||||
bo.InitialInterval = 200 * time.Millisecond
|
bo.InitialInterval = 200 * time.Millisecond
|
||||||
bo.MaxInterval = 5 * time.Second
|
bo.MaxInterval = 5 * time.Second
|
||||||
|
bo.MaxElapsedTime = 20 * time.Second
|
||||||
|
|
||||||
operation := func() (*http.Response, error) {
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
resp, err := a.signedPost(uri, content, response)
|
|
||||||
|
var resp *http.Response
|
||||||
|
operation := func() error {
|
||||||
|
var err error
|
||||||
|
resp, err = a.signedPost(uri, content, response)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Retry if the nonce was invalidated
|
// Retry if the nonce was invalidated
|
||||||
var e *acme.NonceError
|
var e *acme.NonceError
|
||||||
if errors.As(err, &e) {
|
if errors.As(err, &e) {
|
||||||
return resp, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, backoff.Permanent(err)
|
cancel()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
notify := func(err error, duration time.Duration) {
|
notify := func(err error, duration time.Duration) {
|
||||||
log.Infof("retry due to: %v", err)
|
log.Infof("retry due to: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return backoff.Retry(ctx, operation,
|
err := backoff.RetryNotify(operation, backoff.WithContext(bo, ctx), notify)
|
||||||
backoff.WithBackOff(bo),
|
if err != nil {
|
||||||
backoff.WithMaxElapsedTime(20*time.Second),
|
return resp, err
|
||||||
backoff.WithNotify(notify))
|
}
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *Core) signedPost(uri string, content []byte, response any) (*http.Response, error) {
|
func (a *Core) signedPost(uri string, content []byte, response interface{}) (*http.Response, error) {
|
||||||
signedContent, err := a.jws.SignContent(uri, content)
|
signedContent, err := a.jws.SignContent(uri, content)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err)
|
return nil, fmt.Errorf("failed to post JWS message: failed to sign content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
signedBody := bytes.NewBufferString(signedContent.FullSerialize())
|
signedBody := bytes.NewBuffer([]byte(signedContent.FullSerialize()))
|
||||||
|
|
||||||
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
|
resp, err := a.doer.Post(uri, signedBody, "application/jose+json", response)
|
||||||
|
|
||||||
|
|
@ -155,7 +161,6 @@ func getDirectory(do *sender.Doer, caDirURL string) (acme.Directory, error) {
|
||||||
if dir.NewAccountURL == "" {
|
if dir.NewAccountURL == "" {
|
||||||
return dir, errors.New("directory missing new registration URL")
|
return dir, errors.New("directory missing new registration URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
if dir.NewOrderURL == "" {
|
if dir.NewOrderURL == "" {
|
||||||
return dir, errors.New("directory missing new order URL")
|
return dir, errors.New("directory missing new order URL")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
vendor/github.com/go-acme/lego/v4/acme/api/authorization.go
generated
vendored
4
vendor/github.com/go-acme/lego/v4/acme/api/authorization.go
generated
vendored
|
|
@ -15,12 +15,10 @@ func (c *AuthorizationService) Get(authzURL string) (acme.Authorization, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var authz acme.Authorization
|
var authz acme.Authorization
|
||||||
|
|
||||||
_, err := c.core.postAsGet(authzURL, &authz)
|
_, err := c.core.postAsGet(authzURL, &authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.Authorization{}, err
|
return acme.Authorization{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return authz, nil
|
return authz, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -31,8 +29,6 @@ func (c *AuthorizationService) Deactivate(authzURL string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
var disabledAuth acme.Authorization
|
var disabledAuth acme.Authorization
|
||||||
|
|
||||||
_, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth)
|
_, err := c.core.post(authzURL, acme.Authorization{Status: acme.StatusDeactivated}, &disabledAuth)
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
57
vendor/github.com/go-acme/lego/v4/acme/api/certificate.go
generated
vendored
57
vendor/github.com/go-acme/lego/v4/acme/api/certificate.go
generated
vendored
|
|
@ -1,13 +1,15 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto/x509"
|
||||||
"encoding/pem"
|
"encoding/pem"
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
"github.com/go-acme/lego/v4/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// maxBodySize is the maximum size of body that we will read.
|
// maxBodySize is the maximum size of body that we will read.
|
||||||
|
|
@ -37,7 +39,7 @@ func (c *CertificateService) GetAll(certURL string, bundle bool) (map[string]*ac
|
||||||
certs := map[string]*acme.RawCertificate{certURL: cert}
|
certs := map[string]*acme.RawCertificate{certURL: cert}
|
||||||
|
|
||||||
// URLs of "alternate" link relation
|
// URLs of "alternate" link relation
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4.2
|
// - https://tools.ietf.org/html/rfc8555#section-7.4.2
|
||||||
alts := getLinks(headers, "alternate")
|
alts := getLinks(headers, "alternate")
|
||||||
|
|
||||||
for _, alt := range alts {
|
for _, alt := range alts {
|
||||||
|
|
@ -69,27 +71,62 @@ func (c *CertificateService) get(certURL string, bundle bool) (*acme.RawCertific
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
data, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, resp.Header, err
|
return nil, resp.Header, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cert := c.getCertificateChain(data, bundle)
|
cert := c.getCertificateChain(data, resp.Header, bundle, certURL)
|
||||||
|
|
||||||
return cert, resp.Header, err
|
return cert, resp.Header, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCertificateChain Returns the certificate and the issuer certificate.
|
// getCertificateChain Returns the certificate and the issuer certificate.
|
||||||
func (c *CertificateService) getCertificateChain(cert []byte, bundle bool) *acme.RawCertificate {
|
func (c *CertificateService) getCertificateChain(cert []byte, headers http.Header, bundle bool, certURL string) *acme.RawCertificate {
|
||||||
// Get issuerCert from bundled response from Let's Encrypt
|
// Get issuerCert from bundled response from Let's Encrypt
|
||||||
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
// See https://community.letsencrypt.org/t/acme-v2-no-up-link-in-response/64962
|
||||||
_, issuer := pem.Decode(cert)
|
_, issuer := pem.Decode(cert)
|
||||||
|
if issuer != nil {
|
||||||
|
return &acme.RawCertificate{Cert: cert, Issuer: issuer}
|
||||||
|
}
|
||||||
|
|
||||||
// If bundle is false, we want to return a single certificate.
|
// The issuer certificate link may be supplied via an "up" link
|
||||||
// To do this, we remove the issuer cert(s) from the issued cert.
|
// in the response headers of a new certificate.
|
||||||
if !bundle {
|
// See https://tools.ietf.org/html/rfc8555#section-7.4.2
|
||||||
cert = bytes.TrimSuffix(cert, issuer)
|
up := getLink(headers, "up")
|
||||||
|
|
||||||
|
issuer, err := c.getIssuerFromLink(up)
|
||||||
|
if err != nil {
|
||||||
|
// If we fail to acquire the issuer cert, return the issued certificate - do not fail.
|
||||||
|
log.Warnf("acme: Could not bundle issuer certificate [%s]: %v", certURL, err)
|
||||||
|
} else if len(issuer) > 0 {
|
||||||
|
// If bundle is true, we want to return a certificate bundle.
|
||||||
|
// To do this, we append the issuer cert to the issued cert.
|
||||||
|
if bundle {
|
||||||
|
cert = append(cert, issuer...)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return &acme.RawCertificate{Cert: cert, Issuer: issuer}
|
return &acme.RawCertificate{Cert: cert, Issuer: issuer}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getIssuerFromLink requests the issuer certificate.
|
||||||
|
func (c *CertificateService) getIssuerFromLink(up string) ([]byte, error) {
|
||||||
|
if up == "" {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("acme: Requesting issuer cert from %s", up)
|
||||||
|
|
||||||
|
cert, _, err := c.get(up, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = x509.ParseCertificate(cert.Cert)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return certcrypto.PEMEncode(certcrypto.DERCertificateBytes(cert.Cert)), nil
|
||||||
|
}
|
||||||
|
|
|
||||||
4
vendor/github.com/go-acme/lego/v4/acme/api/challenge.go
generated
vendored
4
vendor/github.com/go-acme/lego/v4/acme/api/challenge.go
generated
vendored
|
|
@ -17,7 +17,6 @@ func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||||
// Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`.
|
// Challenge initiation is done by sending a JWS payload containing the trivial JSON object `{}`.
|
||||||
// We use an empty struct instance as the postJSON payload here to achieve this result.
|
// We use an empty struct instance as the postJSON payload here to achieve this result.
|
||||||
var chlng acme.ExtendedChallenge
|
var chlng acme.ExtendedChallenge
|
||||||
|
|
||||||
resp, err := c.core.post(chlgURL, struct{}{}, &chlng)
|
resp, err := c.core.post(chlgURL, struct{}{}, &chlng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.ExtendedChallenge{}, err
|
return acme.ExtendedChallenge{}, err
|
||||||
|
|
@ -25,7 +24,6 @@ func (c *ChallengeService) New(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||||
|
|
||||||
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
||||||
chlng.RetryAfter = getRetryAfter(resp)
|
chlng.RetryAfter = getRetryAfter(resp)
|
||||||
|
|
||||||
return chlng, nil
|
return chlng, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,7 +34,6 @@ func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var chlng acme.ExtendedChallenge
|
var chlng acme.ExtendedChallenge
|
||||||
|
|
||||||
resp, err := c.core.postAsGet(chlgURL, &chlng)
|
resp, err := c.core.postAsGet(chlgURL, &chlng)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.ExtendedChallenge{}, err
|
return acme.ExtendedChallenge{}, err
|
||||||
|
|
@ -44,6 +41,5 @@ func (c *ChallengeService) Get(chlgURL string) (acme.ExtendedChallenge, error) {
|
||||||
|
|
||||||
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
chlng.AuthorizationURL = getLink(resp.Header, "up")
|
||||||
chlng.RetryAfter = getRetryAfter(resp)
|
chlng.RetryAfter = getRetryAfter(resp)
|
||||||
|
|
||||||
return chlng, nil
|
return chlng, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
49
vendor/github.com/go-acme/lego/v4/acme/api/identifier.go
generated
vendored
49
vendor/github.com/go-acme/lego/v4/acme/api/identifier.go
generated
vendored
|
|
@ -1,49 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cmp"
|
|
||||||
"maps"
|
|
||||||
"net"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
|
||||||
)
|
|
||||||
|
|
||||||
func createIdentifiers(domains []string) []acme.Identifier {
|
|
||||||
uniqIdentifiers := make(map[string]acme.Identifier)
|
|
||||||
|
|
||||||
for _, domain := range domains {
|
|
||||||
if _, ok := uniqIdentifiers[domain]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ident := acme.Identifier{Value: domain, Type: "dns"}
|
|
||||||
|
|
||||||
if net.ParseIP(domain) != nil {
|
|
||||||
ident.Type = "ip"
|
|
||||||
}
|
|
||||||
|
|
||||||
uniqIdentifiers[domain] = ident
|
|
||||||
}
|
|
||||||
|
|
||||||
return slices.AppendSeq(make([]acme.Identifier, 0, len(uniqIdentifiers)), maps.Values(uniqIdentifiers))
|
|
||||||
}
|
|
||||||
|
|
||||||
// compareIdentifiers compares 2 slices of [acme.Identifier].
|
|
||||||
func compareIdentifiers(a, b []acme.Identifier) int {
|
|
||||||
// Clones slices to avoid modifying original slices.
|
|
||||||
right := slices.Clone(a)
|
|
||||||
left := slices.Clone(b)
|
|
||||||
|
|
||||||
slices.SortStableFunc(right, compareIdentifier)
|
|
||||||
slices.SortStableFunc(left, compareIdentifier)
|
|
||||||
|
|
||||||
return slices.CompareFunc(right, left, compareIdentifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
func compareIdentifier(right, left acme.Identifier) int {
|
|
||||||
return cmp.Or(
|
|
||||||
cmp.Compare(right.Type, left.Type),
|
|
||||||
cmp.Compare(right.Value, left.Value),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
8
vendor/github.com/go-acme/lego/v4/acme/api/internal/nonces/nonce_manager.go
generated
vendored
8
vendor/github.com/go-acme/lego/v4/acme/api/internal/nonces/nonce_manager.go
generated
vendored
|
|
@ -11,11 +11,10 @@ import (
|
||||||
|
|
||||||
// Manager Manages nonces.
|
// Manager Manages nonces.
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
sync.Mutex
|
|
||||||
|
|
||||||
do *sender.Doer
|
do *sender.Doer
|
||||||
nonceURL string
|
nonceURL string
|
||||||
nonces []string
|
nonces []string
|
||||||
|
sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewManager Creates a new Manager.
|
// NewManager Creates a new Manager.
|
||||||
|
|
@ -37,7 +36,6 @@ func (n *Manager) Pop() (string, bool) {
|
||||||
|
|
||||||
nonce := n.nonces[len(n.nonces)-1]
|
nonce := n.nonces[len(n.nonces)-1]
|
||||||
n.nonces = n.nonces[:len(n.nonces)-1]
|
n.nonces = n.nonces[:len(n.nonces)-1]
|
||||||
|
|
||||||
return nonce, true
|
return nonce, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -45,7 +43,6 @@ func (n *Manager) Pop() (string, bool) {
|
||||||
func (n *Manager) Push(nonce string) {
|
func (n *Manager) Push(nonce string) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
|
|
||||||
n.nonces = append(n.nonces, nonce)
|
n.nonces = append(n.nonces, nonce)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,7 +51,6 @@ func (n *Manager) Nonce() (string, error) {
|
||||||
if nonce, ok := n.Pop(); ok {
|
if nonce, ok := n.Pop(); ok {
|
||||||
return nonce, nil
|
return nonce, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return n.getNonce()
|
return n.getNonce()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -67,7 +63,7 @@ func (n *Manager) getNonce() (string, error) {
|
||||||
return GetFromResponse(resp)
|
return GetFromResponse(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFromResponse Extracts a nonce from an HTTP response.
|
// GetFromResponse Extracts a nonce from a HTTP response.
|
||||||
func GetFromResponse(resp *http.Response) (string, error) {
|
func GetFromResponse(resp *http.Response) (string, error) {
|
||||||
if resp == nil {
|
if resp == nil {
|
||||||
return "", errors.New("nil response")
|
return "", errors.New("nil response")
|
||||||
|
|
|
||||||
10
vendor/github.com/go-acme/lego/v4/acme/api/internal/secure/jws.go
generated
vendored
10
vendor/github.com/go-acme/lego/v4/acme/api/internal/secure/jws.go
generated
vendored
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
"github.com/go-acme/lego/v4/acme/api/internal/nonces"
|
||||||
jose "github.com/go-jose/go-jose/v4"
|
jose "gopkg.in/square/go-jose.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// JWS Represents a JWS.
|
// JWS Represents a JWS.
|
||||||
|
|
@ -36,7 +36,6 @@ func (j *JWS) SetKid(kid string) {
|
||||||
// SignContent Signs a content with the JWS.
|
// SignContent Signs a content with the JWS.
|
||||||
func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
|
func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, error) {
|
||||||
var alg jose.SignatureAlgorithm
|
var alg jose.SignatureAlgorithm
|
||||||
|
|
||||||
switch k := j.privKey.(type) {
|
switch k := j.privKey.(type) {
|
||||||
case *rsa.PrivateKey:
|
case *rsa.PrivateKey:
|
||||||
alg = jose.RS256
|
alg = jose.RS256
|
||||||
|
|
@ -55,7 +54,7 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e
|
||||||
|
|
||||||
options := jose.SignerOptions{
|
options := jose.SignerOptions{
|
||||||
NonceSource: j.nonces,
|
NonceSource: j.nonces,
|
||||||
ExtraHeaders: map[jose.HeaderKey]any{
|
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||||
"url": url,
|
"url": url,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
@ -73,14 +72,12 @@ func (j *JWS) SignContent(url string, content []byte) (*jose.JSONWebSignature, e
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to sign content: %w", err)
|
return nil, fmt.Errorf("failed to sign content: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return signed, nil
|
return signed, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SignEABContent Signs an external account binding content with the JWS.
|
// SignEABContent Signs an external account binding content with the JWS.
|
||||||
func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
|
func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignature, error) {
|
||||||
jwk := jose.JSONWebKey{Key: j.privKey}
|
jwk := jose.JSONWebKey{Key: j.privKey}
|
||||||
|
|
||||||
jwkJSON, err := jwk.Public().MarshalJSON()
|
jwkJSON, err := jwk.Public().MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err)
|
return nil, fmt.Errorf("acme: error encoding eab jwk key: %w", err)
|
||||||
|
|
@ -90,7 +87,7 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
|
||||||
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
|
jose.SigningKey{Algorithm: jose.HS256, Key: hmac},
|
||||||
&jose.SignerOptions{
|
&jose.SignerOptions{
|
||||||
EmbedJWK: false,
|
EmbedJWK: false,
|
||||||
ExtraHeaders: map[jose.HeaderKey]any{
|
ExtraHeaders: map[jose.HeaderKey]interface{}{
|
||||||
"kid": kid,
|
"kid": kid,
|
||||||
"url": url,
|
"url": url,
|
||||||
},
|
},
|
||||||
|
|
@ -111,7 +108,6 @@ func (j *JWS) SignEABContent(url, kid string, hmac []byte) (*jose.JSONWebSignatu
|
||||||
// GetKeyAuthorization Gets the key authorization for a token.
|
// GetKeyAuthorization Gets the key authorization for a token.
|
||||||
func (j *JWS) GetKeyAuthorization(token string) (string, error) {
|
func (j *JWS) GetKeyAuthorization(token string) (string, error) {
|
||||||
var publicKey crypto.PublicKey
|
var publicKey crypto.PublicKey
|
||||||
|
|
||||||
switch k := j.privKey.(type) {
|
switch k := j.privKey.(type) {
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
publicKey = k.Public()
|
publicKey = k.Public()
|
||||||
|
|
|
||||||
48
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/sender.go
generated
vendored
48
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/sender.go
generated
vendored
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -27,8 +28,6 @@ type Doer struct {
|
||||||
|
|
||||||
// NewDoer Creates a new Doer.
|
// NewDoer Creates a new Doer.
|
||||||
func NewDoer(client *http.Client, userAgent string) *Doer {
|
func NewDoer(client *http.Client, userAgent string) *Doer {
|
||||||
client.Transport = newHTTPSOnly(client)
|
|
||||||
|
|
||||||
return &Doer{
|
return &Doer{
|
||||||
httpClient: client,
|
httpClient: client,
|
||||||
userAgent: userAgent,
|
userAgent: userAgent,
|
||||||
|
|
@ -37,7 +36,7 @@ func NewDoer(client *http.Client, userAgent string) *Doer {
|
||||||
|
|
||||||
// Get performs a GET request with a proper User-Agent string.
|
// Get performs a GET request with a proper User-Agent string.
|
||||||
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
||||||
func (d *Doer) Get(url string, response any) (*http.Response, error) {
|
func (d *Doer) Get(url string, response interface{}) (*http.Response, error) {
|
||||||
req, err := d.newRequest(http.MethodGet, url, nil)
|
req, err := d.newRequest(http.MethodGet, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -59,7 +58,7 @@ func (d *Doer) Head(url string) (*http.Response, error) {
|
||||||
|
|
||||||
// Post performs a POST request with a proper User-Agent string.
|
// Post performs a POST request with a proper User-Agent string.
|
||||||
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
// If "response" is not provided, callers should close resp.Body when done reading from it.
|
||||||
func (d *Doer) Post(url string, body io.Reader, bodyType string, response any) (*http.Response, error) {
|
func (d *Doer) Post(url string, body io.Reader, bodyType string, response interface{}) (*http.Response, error) {
|
||||||
req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType))
|
req, err := d.newRequest(http.MethodPost, url, body, contentType(bodyType))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -86,7 +85,7 @@ func (d *Doer) newRequest(method, uri string, body io.Reader, opts ...RequestOpt
|
||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Doer) do(req *http.Request, response any) (*http.Response, error) {
|
func (d *Doer) do(req *http.Request, response interface{}) (*http.Response, error) {
|
||||||
resp, err := d.httpClient.Do(req)
|
resp, err := d.httpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -97,7 +96,7 @@ func (d *Doer) do(req *http.Request, response any) (*http.Response, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if response != nil {
|
if response != nil {
|
||||||
raw, err := io.ReadAll(resp.Body)
|
raw, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, err
|
return resp, err
|
||||||
}
|
}
|
||||||
|
|
@ -121,13 +120,12 @@ func (d *Doer) formatUserAgent() string {
|
||||||
|
|
||||||
func checkError(req *http.Request, resp *http.Response) error {
|
func checkError(req *http.Request, resp *http.Response) error {
|
||||||
if resp.StatusCode >= http.StatusBadRequest {
|
if resp.StatusCode >= http.StatusBadRequest {
|
||||||
body, err := io.ReadAll(resp.Body)
|
body, err := ioutil.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
return fmt.Errorf("%d :: %s :: %s :: %w", resp.StatusCode, req.Method, req.URL, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errorDetails *acme.ProblemDetails
|
var errorDetails *acme.ProblemDetails
|
||||||
|
|
||||||
err = json.Unmarshal(body, &errorDetails)
|
err = json.Unmarshal(body, &errorDetails)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
|
return fmt.Errorf("%d ::%s :: %s :: %w :: %s", resp.StatusCode, req.Method, req.URL, err, string(body))
|
||||||
|
|
@ -136,46 +134,12 @@ func checkError(req *http.Request, resp *http.Response) error {
|
||||||
errorDetails.Method = req.Method
|
errorDetails.Method = req.Method
|
||||||
errorDetails.URL = req.URL.String()
|
errorDetails.URL = req.URL.String()
|
||||||
|
|
||||||
if errorDetails.HTTPStatus == 0 {
|
|
||||||
errorDetails.HTTPStatus = resp.StatusCode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for errors we handle specifically
|
// Check for errors we handle specifically
|
||||||
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
|
if errorDetails.HTTPStatus == http.StatusBadRequest && errorDetails.Type == acme.BadNonceErr {
|
||||||
return &acme.NonceError{ProblemDetails: errorDetails}
|
return &acme.NonceError{ProblemDetails: errorDetails}
|
||||||
}
|
}
|
||||||
|
|
||||||
if errorDetails.HTTPStatus == http.StatusConflict && errorDetails.Type == acme.AlreadyReplacedErr {
|
|
||||||
return &acme.AlreadyReplacedError{ProblemDetails: errorDetails}
|
|
||||||
}
|
|
||||||
|
|
||||||
return errorDetails
|
return errorDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpsOnly struct {
|
|
||||||
rt http.RoundTripper
|
|
||||||
}
|
|
||||||
|
|
||||||
func newHTTPSOnly(client *http.Client) *httpsOnly {
|
|
||||||
if client.Transport == nil {
|
|
||||||
return &httpsOnly{rt: http.DefaultTransport}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &httpsOnly{rt: client.Transport}
|
|
||||||
}
|
|
||||||
|
|
||||||
// RoundTrip ensure HTTPS is used.
|
|
||||||
// Each ACME function is accomplished by the client sending a sequence of HTTPS requests to the server [RFC2818],
|
|
||||||
// carrying JSON messages [RFC8259].
|
|
||||||
// Use of HTTPS is REQUIRED.
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc8555#section-6.1
|
|
||||||
func (r *httpsOnly) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
||||||
if req.URL.Scheme != "https" {
|
|
||||||
return nil, fmt.Errorf("HTTPS is required: %s", req.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.rt.RoundTrip(req)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
7
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/useragent.go
generated
vendored
7
vendor/github.com/go-acme/lego/v4/acme/api/internal/sender/useragent.go
generated
vendored
|
|
@ -1,10 +1,11 @@
|
||||||
// Code generated by 'internal/releaser'; DO NOT EDIT.
|
|
||||||
|
|
||||||
package sender
|
package sender
|
||||||
|
|
||||||
|
// CODE GENERATED AUTOMATICALLY
|
||||||
|
// THIS FILE MUST NOT BE EDITED BY HAND
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// ourUserAgent is the User-Agent of this underlying library package.
|
// ourUserAgent is the User-Agent of this underlying library package.
|
||||||
ourUserAgent = "xenolf-acme/4.29.0"
|
ourUserAgent = "xenolf-acme/4.4.0"
|
||||||
|
|
||||||
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
||||||
// values: detach|release
|
// values: detach|release
|
||||||
|
|
|
||||||
83
vendor/github.com/go-acme/lego/v4/acme/api/order.go
generated
vendored
83
vendor/github.com/go-acme/lego/v4/acme/api/order.go
generated
vendored
|
|
@ -3,98 +3,27 @@ package api
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"slices"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrderOptions used to create an order (optional).
|
|
||||||
type OrderOptions struct {
|
|
||||||
NotBefore time.Time
|
|
||||||
NotAfter time.Time
|
|
||||||
|
|
||||||
// A string uniquely identifying the profile
|
|
||||||
// which will be used to affect issuance of the certificate requested by this Order.
|
|
||||||
// - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4
|
|
||||||
Profile string
|
|
||||||
|
|
||||||
// A string uniquely identifying a previously-issued certificate which this
|
|
||||||
// order is intended to replace.
|
|
||||||
// - https://www.rfc-editor.org/rfc/rfc9773.html#section-5
|
|
||||||
ReplacesCertID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type OrderService service
|
type OrderService service
|
||||||
|
|
||||||
// New Creates a new order.
|
// New Creates a new order.
|
||||||
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
func (o *OrderService) New(domains []string) (acme.ExtendedOrder, error) {
|
||||||
return o.NewWithOptions(domains, nil)
|
var identifiers []acme.Identifier
|
||||||
}
|
for _, domain := range domains {
|
||||||
|
identifiers = append(identifiers, acme.Identifier{Type: "dns", Value: domain})
|
||||||
// NewWithOptions Creates a new order.
|
|
||||||
func (o *OrderService) NewWithOptions(domains []string, opts *OrderOptions) (acme.ExtendedOrder, error) {
|
|
||||||
orderReq := acme.Order{Identifiers: createIdentifiers(domains)}
|
|
||||||
|
|
||||||
if opts != nil {
|
|
||||||
if !opts.NotAfter.IsZero() {
|
|
||||||
orderReq.NotAfter = opts.NotAfter.Format(time.RFC3339)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.NotBefore.IsZero() {
|
orderReq := acme.Order{Identifiers: identifiers}
|
||||||
orderReq.NotBefore = opts.NotBefore.Format(time.RFC3339)
|
|
||||||
}
|
|
||||||
|
|
||||||
if o.core.GetDirectory().RenewalInfo != "" {
|
|
||||||
orderReq.Replaces = opts.ReplacesCertID
|
|
||||||
}
|
|
||||||
|
|
||||||
if opts.Profile != "" {
|
|
||||||
orderReq.Profile = opts.Profile
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var order acme.Order
|
var order acme.Order
|
||||||
|
|
||||||
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
resp, err := o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
are := &acme.AlreadyReplacedError{}
|
|
||||||
if !errors.As(err, &are) {
|
|
||||||
return acme.ExtendedOrder{}, err
|
return acme.ExtendedOrder{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the Server rejects the request because the identified certificate has already been marked as replaced,
|
|
||||||
// it MUST return an HTTP 409 (Conflict) with a problem document of type "alreadyReplaced" (see Section 7.4).
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9773.html#section-5
|
|
||||||
orderReq.Replaces = ""
|
|
||||||
|
|
||||||
resp, err = o.core.post(o.core.GetDirectory().NewOrderURL, orderReq, &order)
|
|
||||||
if err != nil {
|
|
||||||
return acme.ExtendedOrder{}, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The server MUST return an error if it cannot fulfill the request as specified,
|
|
||||||
// and it MUST NOT issue a certificate with contents other than those requested.
|
|
||||||
// If the server requires the request to be modified in a certain way,
|
|
||||||
// it should indicate the required changes using an appropriate error type and description.
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555#section-7.4
|
|
||||||
//
|
|
||||||
// Some ACME servers don't return an error,
|
|
||||||
// and/or change the order identifiers in the response,
|
|
||||||
// so we need to ensure that the identifiers are the same as requested.
|
|
||||||
// Deduplication by the server is allowed.
|
|
||||||
if compareIdentifiers(orderReq.Identifiers, order.Identifiers) != 0 {
|
|
||||||
// Sorts identifiers to avoid error message ambiguities about the order of the identifiers.
|
|
||||||
slices.SortStableFunc(orderReq.Identifiers, compareIdentifier)
|
|
||||||
slices.SortStableFunc(order.Identifiers, compareIdentifier)
|
|
||||||
|
|
||||||
return acme.ExtendedOrder{},
|
|
||||||
fmt.Errorf("order identifiers have been modified by the ACME server (RFC8555 §7.4): %+v != %+v",
|
|
||||||
orderReq.Identifiers, order.Identifiers)
|
|
||||||
}
|
|
||||||
|
|
||||||
return acme.ExtendedOrder{
|
return acme.ExtendedOrder{
|
||||||
Order: order,
|
Order: order,
|
||||||
Location: resp.Header.Get("Location"),
|
Location: resp.Header.Get("Location"),
|
||||||
|
|
@ -108,7 +37,6 @@ func (o *OrderService) Get(orderURL string) (acme.ExtendedOrder, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var order acme.Order
|
var order acme.Order
|
||||||
|
|
||||||
_, err := o.core.postAsGet(orderURL, &order)
|
_, err := o.core.postAsGet(orderURL, &order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.ExtendedOrder{}, err
|
return acme.ExtendedOrder{}, err
|
||||||
|
|
@ -124,14 +52,13 @@ func (o *OrderService) UpdateForCSR(orderURL string, csr []byte) (acme.ExtendedO
|
||||||
}
|
}
|
||||||
|
|
||||||
var order acme.Order
|
var order acme.Order
|
||||||
|
|
||||||
_, err := o.core.post(orderURL, csrMsg, &order)
|
_, err := o.core.post(orderURL, csrMsg, &order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return acme.ExtendedOrder{}, err
|
return acme.ExtendedOrder{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if order.Status == acme.StatusInvalid {
|
if order.Status == acme.StatusInvalid {
|
||||||
return acme.ExtendedOrder{}, fmt.Errorf("invalid order: %w", order.Err())
|
return acme.ExtendedOrder{}, order.Error
|
||||||
}
|
}
|
||||||
|
|
||||||
return acme.ExtendedOrder{Order: order}, nil
|
return acme.ExtendedOrder{Order: order}, nil
|
||||||
|
|
|
||||||
28
vendor/github.com/go-acme/lego/v4/acme/api/renewal.go
generated
vendored
28
vendor/github.com/go-acme/lego/v4/acme/api/renewal.go
generated
vendored
|
|
@ -1,28 +0,0 @@
|
||||||
package api
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ErrNoARI is returned when the server does not advertise a renewal info endpoint.
|
|
||||||
var ErrNoARI = errors.New("renewalInfo[get/post]: server does not advertise a renewal info endpoint")
|
|
||||||
|
|
||||||
// GetRenewalInfo GETs renewal information for a certificate from the renewalInfo endpoint.
|
|
||||||
// This is used to determine if a certificate needs to be renewed.
|
|
||||||
//
|
|
||||||
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
|
|
||||||
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
|
|
||||||
//
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9773.html
|
|
||||||
func (c *CertificateService) GetRenewalInfo(certID string) (*http.Response, error) {
|
|
||||||
if c.core.GetDirectory().RenewalInfo == "" {
|
|
||||||
return nil, ErrNoARI
|
|
||||||
}
|
|
||||||
|
|
||||||
if certID == "" {
|
|
||||||
return nil, errors.New("renewalInfo[get]: 'certID' cannot be empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.core.HTTPClient.Get(c.core.GetDirectory().RenewalInfo + "/" + certID)
|
|
||||||
}
|
|
||||||
2
vendor/github.com/go-acme/lego/v4/acme/api/service.go
generated
vendored
2
vendor/github.com/go-acme/lego/v4/acme/api/service.go
generated
vendored
|
|
@ -23,13 +23,11 @@ func getLinks(header http.Header, rel string) []string {
|
||||||
linkExpr := regexp.MustCompile(`<(.+?)>(?:;[^;]+)*?;\s*rel="(.+?)"`)
|
linkExpr := regexp.MustCompile(`<(.+?)>(?:;[^;]+)*?;\s*rel="(.+?)"`)
|
||||||
|
|
||||||
var links []string
|
var links []string
|
||||||
|
|
||||||
for _, link := range header["Link"] {
|
for _, link := range header["Link"] {
|
||||||
for _, m := range linkExpr.FindAllStringSubmatch(link, -1) {
|
for _, m := range linkExpr.FindAllStringSubmatch(link, -1) {
|
||||||
if len(m) != 3 {
|
if len(m) != 3 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if m[2] == rel {
|
if m[2] == rel {
|
||||||
links = append(links, m[1])
|
links = append(links, m[1])
|
||||||
}
|
}
|
||||||
|
|
|
||||||
143
vendor/github.com/go-acme/lego/v4/acme/commons.go
generated
vendored
143
vendor/github.com/go-acme/lego/v4/acme/commons.go
generated
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
// Package acme contains all objects related the ACME endpoints.
|
// Package acme contains all objects related the ACME endpoints.
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html
|
// https://tools.ietf.org/html/rfc8555
|
||||||
package acme
|
package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -7,38 +7,20 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ACME status values of Account, Order, Authorization and Challenge objects.
|
// Challenge statuses.
|
||||||
// See https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.6 for details.
|
// https://tools.ietf.org/html/rfc8555#section-7.1.6
|
||||||
const (
|
const (
|
||||||
|
StatusPending = "pending"
|
||||||
|
StatusInvalid = "invalid"
|
||||||
|
StatusValid = "valid"
|
||||||
|
StatusProcessing = "processing"
|
||||||
StatusDeactivated = "deactivated"
|
StatusDeactivated = "deactivated"
|
||||||
StatusExpired = "expired"
|
StatusExpired = "expired"
|
||||||
StatusInvalid = "invalid"
|
|
||||||
StatusPending = "pending"
|
|
||||||
StatusProcessing = "processing"
|
|
||||||
StatusReady = "ready"
|
|
||||||
StatusRevoked = "revoked"
|
StatusRevoked = "revoked"
|
||||||
StatusUnknown = "unknown"
|
|
||||||
StatusValid = "valid"
|
|
||||||
)
|
|
||||||
|
|
||||||
// CRL reason codes as defined in RFC 5280.
|
|
||||||
// https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1
|
|
||||||
const (
|
|
||||||
CRLReasonUnspecified uint = 0
|
|
||||||
CRLReasonKeyCompromise uint = 1
|
|
||||||
CRLReasonCACompromise uint = 2
|
|
||||||
CRLReasonAffiliationChanged uint = 3
|
|
||||||
CRLReasonSuperseded uint = 4
|
|
||||||
CRLReasonCessationOfOperation uint = 5
|
|
||||||
CRLReasonCertificateHold uint = 6
|
|
||||||
CRLReasonRemoveFromCRL uint = 8
|
|
||||||
CRLReasonPrivilegeWithdrawn uint = 9
|
|
||||||
CRLReasonAACompromise uint = 10
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Directory the ACME directory object.
|
// Directory the ACME directory object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1
|
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
|
||||||
// - https://www.rfc-editor.org/rfc/rfc9773.html
|
|
||||||
type Directory struct {
|
type Directory struct {
|
||||||
NewNonceURL string `json:"newNonce"`
|
NewNonceURL string `json:"newNonce"`
|
||||||
NewAccountURL string `json:"newAccount"`
|
NewAccountURL string `json:"newAccount"`
|
||||||
|
|
@ -47,11 +29,10 @@ type Directory struct {
|
||||||
RevokeCertURL string `json:"revokeCert"`
|
RevokeCertURL string `json:"revokeCert"`
|
||||||
KeyChangeURL string `json:"keyChange"`
|
KeyChangeURL string `json:"keyChange"`
|
||||||
Meta Meta `json:"meta"`
|
Meta Meta `json:"meta"`
|
||||||
RenewalInfo string `json:"renewalInfo"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Meta the ACME meta object (related to Directory).
|
// Meta the ACME meta object (related to Directory).
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.1
|
// - https://tools.ietf.org/html/rfc8555#section-7.1.1
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
// termsOfService (optional, string):
|
// termsOfService (optional, string):
|
||||||
// A URL identifying the current terms of service.
|
// A URL identifying the current terms of service.
|
||||||
|
|
@ -71,33 +52,27 @@ type Meta struct {
|
||||||
|
|
||||||
// externalAccountRequired (optional, boolean):
|
// externalAccountRequired (optional, boolean):
|
||||||
// If this field is present and set to "true",
|
// If this field is present and set to "true",
|
||||||
// then the CA requires that all new-account requests include an "externalAccountBinding" field
|
// then the CA requires that all new- account requests include an "externalAccountBinding" field
|
||||||
// associating the new account with an external account.
|
// associating the new account with an external account.
|
||||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||||
|
|
||||||
// profiles (optional, object):
|
|
||||||
// A map of profile names to human-readable descriptions of those profiles.
|
|
||||||
// https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-3
|
|
||||||
Profiles map[string]string `json:"profiles"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtendedAccount an extended Account.
|
// ExtendedAccount a extended Account.
|
||||||
type ExtendedAccount struct {
|
type ExtendedAccount struct {
|
||||||
Account
|
Account
|
||||||
|
|
||||||
// Contains the value of the response header `Location`
|
// Contains the value of the response header `Location`
|
||||||
Location string `json:"-"`
|
Location string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Account the ACME account Object.
|
// Account the ACME account Object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.2
|
// - https://tools.ietf.org/html/rfc8555#section-7.1.2
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3
|
// - https://tools.ietf.org/html/rfc8555#section-7.3
|
||||||
type Account struct {
|
type Account struct {
|
||||||
// status (required, string):
|
// status (required, string):
|
||||||
// The status of this account.
|
// The status of this account.
|
||||||
// Possible values are: "valid", "deactivated", and "revoked".
|
// Possible values are: "valid", "deactivated", and "revoked".
|
||||||
// The value "deactivated" should be used to indicate client-initiated deactivation
|
// The value "deactivated" should be used to indicate client-initiated deactivation
|
||||||
// whereas "revoked" should be used to indicate server-initiated deactivation. (See Section 7.1.6)
|
// whereas "revoked" should be used to indicate server- initiated deactivation. (See Section 7.1.6)
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
|
|
||||||
// contact (optional, array of string):
|
// contact (optional, array of string):
|
||||||
|
|
@ -137,7 +112,7 @@ type ExtendedOrder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Order the ACME order Object.
|
// Order the ACME order Object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.3
|
// - https://tools.ietf.org/html/rfc8555#section-7.1.3
|
||||||
type Order struct {
|
type Order struct {
|
||||||
// status (required, string):
|
// status (required, string):
|
||||||
// The status of this order.
|
// The status of this order.
|
||||||
|
|
@ -154,12 +129,6 @@ type Order struct {
|
||||||
// An array of identifier objects that the order pertains to.
|
// An array of identifier objects that the order pertains to.
|
||||||
Identifiers []Identifier `json:"identifiers"`
|
Identifiers []Identifier `json:"identifiers"`
|
||||||
|
|
||||||
// profile (string, optional):
|
|
||||||
// A string uniquely identifying the profile
|
|
||||||
// which will be used to affect issuance of the certificate requested by this Order.
|
|
||||||
// https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4
|
|
||||||
Profile string `json:"profile,omitempty"`
|
|
||||||
|
|
||||||
// notBefore (optional, string):
|
// notBefore (optional, string):
|
||||||
// The requested value of the notBefore field in the certificate,
|
// The requested value of the notBefore field in the certificate,
|
||||||
// in the date format defined in [RFC3339].
|
// in the date format defined in [RFC3339].
|
||||||
|
|
@ -193,24 +162,10 @@ type Order struct {
|
||||||
// certificate (optional, string):
|
// certificate (optional, string):
|
||||||
// A URL for the certificate that has been issued in response to this order
|
// A URL for the certificate that has been issued in response to this order
|
||||||
Certificate string `json:"certificate,omitempty"`
|
Certificate string `json:"certificate,omitempty"`
|
||||||
|
|
||||||
// replaces (optional, string):
|
|
||||||
// replaces (string, optional): A string uniquely identifying a
|
|
||||||
// previously-issued certificate which this order is intended to replace.
|
|
||||||
// - https://www.rfc-editor.org/rfc/rfc9773.html#section-5
|
|
||||||
Replaces string `json:"replaces,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Order) Err() error {
|
|
||||||
if r.Error != nil {
|
|
||||||
return r.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authorization the ACME authorization object.
|
// Authorization the ACME authorization object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4
|
// - https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||||
type Authorization struct {
|
type Authorization struct {
|
||||||
// status (required, string):
|
// status (required, string):
|
||||||
// The status of this authorization.
|
// The status of this authorization.
|
||||||
|
|
@ -221,11 +176,11 @@ type Authorization struct {
|
||||||
// The timestamp after which the server will consider this authorization invalid,
|
// The timestamp after which the server will consider this authorization invalid,
|
||||||
// encoded in the format specified in RFC 3339 [RFC3339].
|
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||||
// This field is REQUIRED for objects with "valid" in the "status" field.
|
// This field is REQUIRED for objects with "valid" in the "status" field.
|
||||||
Expires time.Time `json:"expires,omitzero"`
|
Expires time.Time `json:"expires,omitempty"`
|
||||||
|
|
||||||
// identifier (required, object):
|
// identifier (required, object):
|
||||||
// The identifier that the account is authorized to represent
|
// The identifier that the account is authorized to represent
|
||||||
Identifier Identifier `json:"identifier"`
|
Identifier Identifier `json:"identifier,omitempty"`
|
||||||
|
|
||||||
// challenges (required, array of objects):
|
// challenges (required, array of objects):
|
||||||
// For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier.
|
// For pending authorizations, the challenges that the client can fulfill in order to prove possession of the identifier.
|
||||||
|
|
@ -245,7 +200,6 @@ type Authorization struct {
|
||||||
// ExtendedChallenge a extended Challenge.
|
// ExtendedChallenge a extended Challenge.
|
||||||
type ExtendedChallenge struct {
|
type ExtendedChallenge struct {
|
||||||
Challenge
|
Challenge
|
||||||
|
|
||||||
// Contains the value of the response header `Retry-After`
|
// Contains the value of the response header `Retry-After`
|
||||||
RetryAfter string `json:"-"`
|
RetryAfter string `json:"-"`
|
||||||
// Contains the value of the response header `Link` rel="up"
|
// Contains the value of the response header `Link` rel="up"
|
||||||
|
|
@ -253,8 +207,8 @@ type ExtendedChallenge struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Challenge the ACME challenge object.
|
// Challenge the ACME challenge object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.5
|
// - https://tools.ietf.org/html/rfc8555#section-7.1.5
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-8
|
// - https://tools.ietf.org/html/rfc8555#section-8
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
// type (required, string):
|
// type (required, string):
|
||||||
// The type of challenge encoded in the object.
|
// The type of challenge encoded in the object.
|
||||||
|
|
@ -272,7 +226,7 @@ type Challenge struct {
|
||||||
// The time at which the server validated this challenge,
|
// The time at which the server validated this challenge,
|
||||||
// encoded in the format specified in RFC 3339 [RFC3339].
|
// encoded in the format specified in RFC 3339 [RFC3339].
|
||||||
// This field is REQUIRED if the "status" field is "valid".
|
// This field is REQUIRED if the "status" field is "valid".
|
||||||
Validated time.Time `json:"validated,omitzero"`
|
Validated time.Time `json:"validated,omitempty"`
|
||||||
|
|
||||||
// error (optional, object):
|
// error (optional, object):
|
||||||
// Error that occurred while the server was validating the challenge, if any,
|
// Error that occurred while the server was validating the challenge, if any,
|
||||||
|
|
@ -287,31 +241,23 @@ type Challenge struct {
|
||||||
// It MUST NOT contain any characters outside the base64url alphabet,
|
// It MUST NOT contain any characters outside the base64url alphabet,
|
||||||
// and MUST NOT include base64 padding characters ("=").
|
// and MUST NOT include base64 padding characters ("=").
|
||||||
// See [RFC4086] for additional information on randomness requirements.
|
// See [RFC4086] for additional information on randomness requirements.
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.3
|
// https://tools.ietf.org/html/rfc8555#section-8.3
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.4
|
// https://tools.ietf.org/html/rfc8555#section-8.4
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-8.1
|
// https://tools.ietf.org/html/rfc8555#section-8.1
|
||||||
KeyAuthorization string `json:"keyAuthorization"`
|
KeyAuthorization string `json:"keyAuthorization"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Challenge) Err() error {
|
|
||||||
if c.Error != nil {
|
|
||||||
return c.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Identifier the ACME identifier object.
|
// Identifier the ACME identifier object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-9.7.7
|
// - https://tools.ietf.org/html/rfc8555#section-9.7.7
|
||||||
type Identifier struct {
|
type Identifier struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Value string `json:"value"`
|
Value string `json:"value"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CSRMessage Certificate Signing Request.
|
// CSRMessage Certificate Signing Request.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
|
// - https://tools.ietf.org/html/rfc8555#section-7.4
|
||||||
type CSRMessage struct {
|
type CSRMessage struct {
|
||||||
// csr (required, string):
|
// csr (required, string):
|
||||||
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
// A CSR encoding the parameters for the certificate being requested [RFC2986].
|
||||||
|
|
@ -321,8 +267,8 @@ type CSRMessage struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RevokeCertMessage a certificate revocation message.
|
// RevokeCertMessage a certificate revocation message.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.6
|
// - https://tools.ietf.org/html/rfc8555#section-7.6
|
||||||
// - https://www.rfc-editor.org/rfc/rfc5280.html#section-5.3.1
|
// - https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||||
type RevokeCertMessage struct {
|
type RevokeCertMessage struct {
|
||||||
// certificate (required, string):
|
// certificate (required, string):
|
||||||
// The certificate to be revoked, in the base64url-encoded version of the DER format.
|
// The certificate to be revoked, in the base64url-encoded version of the DER format.
|
||||||
|
|
@ -343,36 +289,3 @@ type RawCertificate struct {
|
||||||
Cert []byte
|
Cert []byte
|
||||||
Issuer []byte
|
Issuer []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
// Window is a window of time.
|
|
||||||
type Window struct {
|
|
||||||
Start time.Time `json:"start"`
|
|
||||||
End time.Time `json:"end"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewalInfoResponse is the response to GET requests made the renewalInfo endpoint.
|
|
||||||
// - (4.1. Getting Renewal Information) https://www.rfc-editor.org/rfc/rfc9773.html
|
|
||||||
type RenewalInfoResponse struct {
|
|
||||||
// SuggestedWindow contains two fields, start and end,
|
|
||||||
// whose values are timestamps which bound the window of time in which the CA recommends renewing the certificate.
|
|
||||||
SuggestedWindow Window `json:"suggestedWindow"`
|
|
||||||
// ExplanationURL is an optional URL pointing to a page which may explain why the suggested renewal window is what it is.
|
|
||||||
// For example, it may be a page explaining the CA's dynamic load-balancing strategy,
|
|
||||||
// or a page documenting which certificates are affected by a mass revocation event.
|
|
||||||
// Callers SHOULD provide this URL to their operator, if present.
|
|
||||||
ExplanationURL string `json:"explanationURL"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewalInfoUpdateRequest is the JWS payload for POST requests made to the renewalInfo endpoint.
|
|
||||||
// - (4.2. RenewalInfo Objects) https://www.rfc-editor.org/rfc/rfc9773.html#section-4.2
|
|
||||||
type RenewalInfoUpdateRequest struct {
|
|
||||||
// CertID is a composite string in the format: base64url(AKI) || '.' || base64url(Serial), where AKI is the
|
|
||||||
// certificate's authority key identifier and Serial is the certificate's serial number. For details, see:
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9773.html#section-4.1
|
|
||||||
CertID string `json:"certID"`
|
|
||||||
// Replaced is required and indicates whether or not the client considers the certificate to have been replaced.
|
|
||||||
// A certificate is considered replaced when its revocation would not disrupt any ongoing services,
|
|
||||||
// for instance because it has been renewed and the new certificate is in use, or because it is no longer in use.
|
|
||||||
// Clients SHOULD NOT send a request where this value is false.
|
|
||||||
Replaced bool `json:"replaced"`
|
|
||||||
}
|
|
||||||
|
|
|
||||||
57
vendor/github.com/go-acme/lego/v4/acme/errors.go
generated
vendored
57
vendor/github.com/go-acme/lego/v4/acme/errors.go
generated
vendored
|
|
@ -2,19 +2,17 @@ package acme
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors types.
|
// Errors types.
|
||||||
const (
|
const (
|
||||||
errNS = "urn:ietf:params:acme:error:"
|
errNS = "urn:ietf:params:acme:error:"
|
||||||
BadNonceErr = errNS + "badNonce"
|
BadNonceErr = errNS + "badNonce"
|
||||||
AlreadyReplacedErr = errNS + "alreadyReplaced"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProblemDetails the problem details object.
|
// ProblemDetails the problem details object.
|
||||||
// - https://www.rfc-editor.org/rfc/rfc7807.html#section-3.1
|
// - https://tools.ietf.org/html/rfc7807#section-3.1
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-7.3.3
|
// - https://tools.ietf.org/html/rfc8555#section-7.3.3
|
||||||
type ProblemDetails struct {
|
type ProblemDetails struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Detail string `json:"detail,omitempty"`
|
Detail string `json:"detail,omitempty"`
|
||||||
|
|
@ -27,34 +25,30 @@ type ProblemDetails struct {
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ProblemDetails) Error() string {
|
|
||||||
var msg strings.Builder
|
|
||||||
|
|
||||||
msg.WriteString(fmt.Sprintf("acme: error: %d", p.HTTPStatus))
|
|
||||||
|
|
||||||
if p.Method != "" || p.URL != "" {
|
|
||||||
msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Method, p.URL))
|
|
||||||
}
|
|
||||||
|
|
||||||
msg.WriteString(fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail))
|
|
||||||
|
|
||||||
for _, sub := range p.SubProblems {
|
|
||||||
msg.WriteString(fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail))
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.Instance != "" {
|
|
||||||
msg.WriteString(", url: " + p.Instance)
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubProblem a "subproblems".
|
// SubProblem a "subproblems".
|
||||||
// - https://www.rfc-editor.org/rfc/rfc8555.html#section-6.7.1
|
// - https://tools.ietf.org/html/rfc8555#section-6.7.1
|
||||||
type SubProblem struct {
|
type SubProblem struct {
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Detail string `json:"detail,omitempty"`
|
Detail string `json:"detail,omitempty"`
|
||||||
Identifier Identifier `json:"identifier"`
|
Identifier Identifier `json:"identifier,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p ProblemDetails) Error() string {
|
||||||
|
msg := fmt.Sprintf("acme: error: %d", p.HTTPStatus)
|
||||||
|
if p.Method != "" || p.URL != "" {
|
||||||
|
msg += fmt.Sprintf(" :: %s :: %s", p.Method, p.URL)
|
||||||
|
}
|
||||||
|
msg += fmt.Sprintf(" :: %s :: %s", p.Type, p.Detail)
|
||||||
|
|
||||||
|
for _, sub := range p.SubProblems {
|
||||||
|
msg += fmt.Sprintf(", problem: %q :: %s", sub.Type, sub.Detail)
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.Instance != "" {
|
||||||
|
msg += ", url: " + p.Instance
|
||||||
|
}
|
||||||
|
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// NonceError represents the error which is returned
|
// NonceError represents the error which is returned
|
||||||
|
|
@ -62,10 +56,3 @@ type SubProblem struct {
|
||||||
type NonceError struct {
|
type NonceError struct {
|
||||||
*ProblemDetails
|
*ProblemDetails
|
||||||
}
|
}
|
||||||
|
|
||||||
// AlreadyReplacedError represents the error which is returned
|
|
||||||
// If the Server rejects the request because the identified certificate has already been marked as replaced.
|
|
||||||
// - https://www.rfc-editor.org/rfc/rfc9773.html#section-5
|
|
||||||
type AlreadyReplacedError struct {
|
|
||||||
*ProblemDetails
|
|
||||||
}
|
|
||||||
|
|
|
||||||
116
vendor/github.com/go-acme/lego/v4/certcrypto/crypto.go
generated
vendored
116
vendor/github.com/go-acme/lego/v4/certcrypto/crypto.go
generated
vendored
|
|
@ -14,8 +14,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -27,7 +25,6 @@ const (
|
||||||
EC256 = KeyType("P256")
|
EC256 = KeyType("P256")
|
||||||
EC384 = KeyType("P384")
|
EC384 = KeyType("P384")
|
||||||
RSA2048 = KeyType("2048")
|
RSA2048 = KeyType("2048")
|
||||||
RSA3072 = KeyType("3072")
|
|
||||||
RSA4096 = KeyType("4096")
|
RSA4096 = KeyType("4096")
|
||||||
RSA8192 = KeyType("8192")
|
RSA8192 = KeyType("8192")
|
||||||
)
|
)
|
||||||
|
|
@ -57,10 +54,8 @@ type DERCertificateBytes []byte
|
||||||
// ParsePEMBundle parses a certificate bundle from top to bottom and returns
|
// ParsePEMBundle parses a certificate bundle from top to bottom and returns
|
||||||
// a slice of x509 certificates. This function will error if no certificates are found.
|
// a slice of x509 certificates. This function will error if no certificates are found.
|
||||||
func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
var (
|
var certificates []*x509.Certificate
|
||||||
certificates []*x509.Certificate
|
var certDERBlock *pem.Block
|
||||||
certDERBlock *pem.Block
|
|
||||||
)
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
certDERBlock, bundle = pem.Decode(bundle)
|
certDERBlock, bundle = pem.Decode(bundle)
|
||||||
|
|
@ -73,7 +68,6 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
certificates = append(certificates, cert)
|
certificates = append(certificates, cert)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,12 +82,9 @@ func ParsePEMBundle(bundle []byte) ([]*x509.Certificate, error) {
|
||||||
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
|
// ParsePEMPrivateKey parses a private key from key, which is a PEM block.
|
||||||
// Borrowed from Go standard library, to handle various private key and PEM block types.
|
// Borrowed from Go standard library, to handle various private key and PEM block types.
|
||||||
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
|
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L291-L308
|
||||||
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238
|
// https://github.com/golang/go/blob/693748e9fa385f1e2c3b91ca9acbb6c0ad2d133d/src/crypto/tls/tls.go#L238)
|
||||||
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
func ParsePEMPrivateKey(key []byte) (crypto.PrivateKey, error) {
|
||||||
keyBlockDER, _ := pem.Decode(key)
|
keyBlockDER, _ := pem.Decode(key)
|
||||||
if keyBlockDER == nil {
|
|
||||||
return nil, errors.New("invalid PEM block")
|
|
||||||
}
|
|
||||||
|
|
||||||
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
|
if keyBlockDER.Type != "PRIVATE KEY" && !strings.HasSuffix(keyBlockDER.Type, " PRIVATE KEY") {
|
||||||
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
|
return nil, fmt.Errorf("unknown PEM header %q", keyBlockDER.Type)
|
||||||
|
|
@ -127,8 +118,6 @@ func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||||
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
return ecdsa.GenerateKey(elliptic.P384(), rand.Reader)
|
||||||
case RSA2048:
|
case RSA2048:
|
||||||
return rsa.GenerateKey(rand.Reader, 2048)
|
return rsa.GenerateKey(rand.Reader, 2048)
|
||||||
case RSA3072:
|
|
||||||
return rsa.GenerateKey(rand.Reader, 3072)
|
|
||||||
case RSA4096:
|
case RSA4096:
|
||||||
return rsa.GenerateKey(rand.Reader, 4096)
|
return rsa.GenerateKey(rand.Reader, 4096)
|
||||||
case RSA8192:
|
case RSA8192:
|
||||||
|
|
@ -138,44 +127,13 @@ func GeneratePrivateKey(keyType KeyType) (crypto.PrivateKey, error) {
|
||||||
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
|
return nil, fmt.Errorf("invalid KeyType: %s", keyType)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: uses [CreateCSR] instead.
|
|
||||||
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
func GenerateCSR(privateKey crypto.PrivateKey, domain string, san []string, mustStaple bool) ([]byte, error) {
|
||||||
return CreateCSR(privateKey, CSROptions{
|
|
||||||
Domain: domain,
|
|
||||||
SAN: san,
|
|
||||||
MustStaple: mustStaple,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type CSROptions struct {
|
|
||||||
Domain string
|
|
||||||
SAN []string
|
|
||||||
MustStaple bool
|
|
||||||
EmailAddresses []string
|
|
||||||
}
|
|
||||||
|
|
||||||
func CreateCSR(privateKey crypto.PrivateKey, opts CSROptions) ([]byte, error) {
|
|
||||||
var (
|
|
||||||
dnsNames []string
|
|
||||||
ipAddresses []net.IP
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, altname := range opts.SAN {
|
|
||||||
if ip := net.ParseIP(altname); ip != nil {
|
|
||||||
ipAddresses = append(ipAddresses, ip)
|
|
||||||
} else {
|
|
||||||
dnsNames = append(dnsNames, altname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template := x509.CertificateRequest{
|
template := x509.CertificateRequest{
|
||||||
Subject: pkix.Name{CommonName: opts.Domain},
|
Subject: pkix.Name{CommonName: domain},
|
||||||
DNSNames: dnsNames,
|
DNSNames: san,
|
||||||
EmailAddresses: opts.EmailAddresses,
|
|
||||||
IPAddresses: ipAddresses,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if opts.MustStaple {
|
if mustStaple {
|
||||||
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
|
template.ExtraExtensions = append(template.ExtraExtensions, pkix.Extension{
|
||||||
Id: tlsFeatureExtensionOID,
|
Id: tlsFeatureExtensionOID,
|
||||||
Value: ocspMustStapleFeature,
|
Value: ocspMustStapleFeature,
|
||||||
|
|
@ -185,13 +143,12 @@ func CreateCSR(privateKey crypto.PrivateKey, opts CSROptions) ([]byte, error) {
|
||||||
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
return x509.CreateCertificateRequest(rand.Reader, &template, privateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
func PEMEncode(data any) []byte {
|
func PEMEncode(data interface{}) []byte {
|
||||||
return pem.EncodeToMemory(PEMBlock(data))
|
return pem.EncodeToMemory(PEMBlock(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func PEMBlock(data any) *pem.Block {
|
func PEMBlock(data interface{}) *pem.Block {
|
||||||
var pemBlock *pem.Block
|
var pemBlock *pem.Block
|
||||||
|
|
||||||
switch key := data.(type) {
|
switch key := data.(type) {
|
||||||
case *ecdsa.PrivateKey:
|
case *ecdsa.PrivateKey:
|
||||||
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
keyBytes, _ := x509.MarshalECPrivateKey(key)
|
||||||
|
|
@ -241,26 +198,6 @@ func ParsePEMCertificate(cert []byte) (*x509.Certificate, error) {
|
||||||
return x509.ParseCertificate(pemBlock.Bytes)
|
return x509.ParseCertificate(pemBlock.Bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCertificateMainDomain(cert *x509.Certificate) (string, error) {
|
|
||||||
return getMainDomain(cert.Subject, cert.DNSNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetCSRMainDomain(cert *x509.CertificateRequest) (string, error) {
|
|
||||||
return getMainDomain(cert.Subject, cert.DNSNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getMainDomain(subject pkix.Name, dnsNames []string) (string, error) {
|
|
||||||
if subject.CommonName == "" && len(dnsNames) == 0 {
|
|
||||||
return "", errors.New("missing domain")
|
|
||||||
}
|
|
||||||
|
|
||||||
if subject.CommonName != "" {
|
|
||||||
return subject.CommonName, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return dnsNames[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExtractDomains(cert *x509.Certificate) []string {
|
func ExtractDomains(cert *x509.Certificate) []string {
|
||||||
var domains []string
|
var domains []string
|
||||||
if cert.Subject.CommonName != "" {
|
if cert.Subject.CommonName != "" {
|
||||||
|
|
@ -272,17 +209,9 @@ func ExtractDomains(cert *x509.Certificate) []string {
|
||||||
if sanDomain == cert.Subject.CommonName {
|
if sanDomain == cert.Subject.CommonName {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
domains = append(domains, sanDomain)
|
domains = append(domains, sanDomain)
|
||||||
}
|
}
|
||||||
|
|
||||||
commonNameIP := net.ParseIP(cert.Subject.CommonName)
|
|
||||||
for _, sanIP := range cert.IPAddresses {
|
|
||||||
if !commonNameIP.Equal(sanIP) {
|
|
||||||
domains = append(domains, sanIP.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains
|
return domains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -294,7 +223,7 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||||
|
|
||||||
// loop over the SubjectAltName DNS names
|
// loop over the SubjectAltName DNS names
|
||||||
for _, sanName := range csr.DNSNames {
|
for _, sanName := range csr.DNSNames {
|
||||||
if slices.Contains(domains, sanName) {
|
if containsSAN(domains, sanName) {
|
||||||
// Duplicate; skip this name
|
// Duplicate; skip this name
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
@ -303,16 +232,18 @@ func ExtractDomainsCSR(csr *x509.CertificateRequest) []string {
|
||||||
domains = append(domains, sanName)
|
domains = append(domains, sanName)
|
||||||
}
|
}
|
||||||
|
|
||||||
cnip := net.ParseIP(csr.Subject.CommonName)
|
|
||||||
for _, sanIP := range csr.IPAddresses {
|
|
||||||
if !cnip.Equal(sanIP) {
|
|
||||||
domains = append(domains, sanIP.String())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return domains
|
return domains
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func containsSAN(domains []string, sanName string) bool {
|
||||||
|
for _, existingName := range domains {
|
||||||
|
if existingName == sanName {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||||
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
derBytes, err := generateDerCert(privateKey, time.Time{}, domain, extensions)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -324,14 +255,13 @@ func GeneratePemCert(privateKey *rsa.PrivateKey, domain string, extensions []pki
|
||||||
|
|
||||||
func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
|
func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain string, extensions []pkix.Extension) ([]byte, error) {
|
||||||
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
|
||||||
|
|
||||||
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if expiration.IsZero() {
|
if expiration.IsZero() {
|
||||||
expiration = time.Now().AddDate(1, 0, 0)
|
expiration = time.Now().Add(365)
|
||||||
}
|
}
|
||||||
|
|
||||||
template := x509.Certificate{
|
template := x509.Certificate{
|
||||||
|
|
@ -344,15 +274,9 @@ func generateDerCert(privateKey *rsa.PrivateKey, expiration time.Time, domain st
|
||||||
|
|
||||||
KeyUsage: x509.KeyUsageKeyEncipherment,
|
KeyUsage: x509.KeyUsageKeyEncipherment,
|
||||||
BasicConstraintsValid: true,
|
BasicConstraintsValid: true,
|
||||||
|
DNSNames: []string{domain},
|
||||||
ExtraExtensions: extensions,
|
ExtraExtensions: extensions,
|
||||||
}
|
}
|
||||||
|
|
||||||
// handling SAN filling as type suspected
|
|
||||||
if ip := net.ParseIP(domain); ip != nil {
|
|
||||||
template.IPAddresses = []net.IP{ip}
|
|
||||||
} else {
|
|
||||||
template.DNSNames = []string{domain}
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
return x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
32
vendor/github.com/go-acme/lego/v4/certificate/authorization.go
generated
vendored
32
vendor/github.com/go-acme/lego/v4/certificate/authorization.go
generated
vendored
|
|
@ -7,10 +7,18 @@ import (
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// overallRequestLimit is the overall number of request per second
|
||||||
|
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
||||||
|
// From the documentation the limitation is 20 requests per second,
|
||||||
|
// but using 20 as value doesn't work but 18 do.
|
||||||
|
overallRequestLimit = 18
|
||||||
|
)
|
||||||
|
|
||||||
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
|
func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authorization, error) {
|
||||||
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
resc, errc := make(chan acme.Authorization), make(chan domainError)
|
||||||
|
|
||||||
delay := time.Second / time.Duration(c.overallRequestLimit)
|
delay := time.Second / overallRequestLimit
|
||||||
|
|
||||||
for _, authzURL := range order.Authorizations {
|
for _, authzURL := range order.Authorizations {
|
||||||
time.Sleep(delay)
|
time.Sleep(delay)
|
||||||
|
|
@ -27,15 +35,13 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
||||||
}
|
}
|
||||||
|
|
||||||
var responses []acme.Authorization
|
var responses []acme.Authorization
|
||||||
|
failures := make(obtainError)
|
||||||
failures := newObtainError()
|
for i := 0; i < len(order.Authorizations); i++ {
|
||||||
|
|
||||||
for range len(order.Authorizations) {
|
|
||||||
select {
|
select {
|
||||||
case res := <-resc:
|
case res := <-resc:
|
||||||
responses = append(responses, res)
|
responses = append(responses, res)
|
||||||
case err := <-errc:
|
case err := <-errc:
|
||||||
failures.Add(err.Domain, err.Error)
|
failures[err.Domain] = err.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,24 +52,28 @@ func (c *Certifier) getAuthorizations(order acme.ExtendedOrder) ([]acme.Authoriz
|
||||||
close(resc)
|
close(resc)
|
||||||
close(errc)
|
close(errc)
|
||||||
|
|
||||||
return responses, failures.Join()
|
// be careful to not return an empty failures map;
|
||||||
|
// even if empty, they become non-nil error values
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return responses, failures
|
||||||
|
}
|
||||||
|
return responses, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder, force bool) {
|
func (c *Certifier) deactivateAuthorizations(order acme.ExtendedOrder) {
|
||||||
for _, authzURL := range order.Authorizations {
|
for _, authzURL := range order.Authorizations {
|
||||||
auth, err := c.core.Authorizations.Get(authzURL)
|
auth, err := c.core.Authorizations.Get(authzURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Infof("Unable to get the authorization for %s: %v", authzURL, err)
|
log.Infof("Unable to get the authorization for: %s", authzURL)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if auth.Status == acme.StatusValid && !force {
|
if auth.Status == acme.StatusValid {
|
||||||
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
|
log.Infof("Skipping deactivating of valid auth: %s", authzURL)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Deactivating auth: %s", authzURL)
|
log.Infof("Deactivating auth: %s", authzURL)
|
||||||
|
|
||||||
if c.core.Authorizations.Deactivate(authzURL) != nil {
|
if c.core.Authorizations.Deactivate(authzURL) != nil {
|
||||||
log.Infof("Unable to deactivate the authorization: %s", authzURL)
|
log.Infof("Unable to deactivate the authorization: %s", authzURL)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
268
vendor/github.com/go-acme/lego/v4/certificate/certificates.go
generated
vendored
268
vendor/github.com/go-acme/lego/v4/certificate/certificates.go
generated
vendored
|
|
@ -7,7 +7,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -22,17 +22,6 @@ import (
|
||||||
"golang.org/x/net/idna"
|
"golang.org/x/net/idna"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
// DefaultOverallRequestLimit is the overall number of request per second
|
|
||||||
// limited on the "new-reg", "new-authz" and "new-cert" endpoints.
|
|
||||||
// From the documentation the limitation is 20 requests per second,
|
|
||||||
// but using 20 as value doesn't work but 18 do.
|
|
||||||
// https://letsencrypt.org/docs/rate-limits/
|
|
||||||
// ZeroSSL has a limit of 7.
|
|
||||||
// https://help.zerossl.com/hc/en-us/articles/17864245480093-Advantages-over-Using-Let-s-Encrypt#h_01HT4Z1JCJFJQFJ1M3P7S085Q9
|
|
||||||
DefaultOverallRequestLimit = 18
|
|
||||||
)
|
|
||||||
|
|
||||||
// maxBodySize is the maximum size of body that we will read.
|
// maxBodySize is the maximum size of body that we will read.
|
||||||
const maxBodySize = 1024 * 1024
|
const maxBodySize = 1024 * 1024
|
||||||
|
|
||||||
|
|
@ -60,61 +49,22 @@ type Resource struct {
|
||||||
// If you do not want that you can supply your own private key in the privateKey parameter.
|
// If you do not want that you can supply your own private key in the privateKey parameter.
|
||||||
// If this parameter is non-nil it will be used instead of generating a new one.
|
// If this parameter is non-nil it will be used instead of generating a new one.
|
||||||
//
|
//
|
||||||
// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle.
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
//
|
|
||||||
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
|
||||||
type ObtainRequest struct {
|
type ObtainRequest struct {
|
||||||
Domains []string
|
Domains []string
|
||||||
|
Bundle bool
|
||||||
PrivateKey crypto.PrivateKey
|
PrivateKey crypto.PrivateKey
|
||||||
MustStaple bool
|
MustStaple bool
|
||||||
EmailAddresses []string
|
|
||||||
|
|
||||||
NotBefore time.Time
|
|
||||||
NotAfter time.Time
|
|
||||||
Bundle bool
|
|
||||||
PreferredChain string
|
PreferredChain string
|
||||||
|
|
||||||
// A string uniquely identifying the profile
|
|
||||||
// which will be used to affect issuance of the certificate requested by this Order.
|
|
||||||
// - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4
|
|
||||||
Profile string
|
|
||||||
|
|
||||||
AlwaysDeactivateAuthorizations bool
|
|
||||||
|
|
||||||
// A string uniquely identifying a previously-issued certificate which this
|
|
||||||
// order is intended to replace.
|
|
||||||
// - https://www.rfc-editor.org/rfc/rfc9773.html#section-5
|
|
||||||
ReplacesCertID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
|
// ObtainForCSRRequest The request to obtain a certificate matching the CSR passed into it.
|
||||||
//
|
//
|
||||||
// If `Bundle` is true, the `[]byte` contains both the issuer certificate and your issued certificate as a bundle.
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
//
|
|
||||||
// If `AlwaysDeactivateAuthorizations` is true, the authorizations are also relinquished if the obtain request was successful.
|
|
||||||
// See https://datatracker.ietf.org/doc/html/rfc8555#section-7.5.2.
|
|
||||||
type ObtainForCSRRequest struct {
|
type ObtainForCSRRequest struct {
|
||||||
CSR *x509.CertificateRequest
|
CSR *x509.CertificateRequest
|
||||||
|
|
||||||
PrivateKey crypto.PrivateKey
|
|
||||||
|
|
||||||
NotBefore time.Time
|
|
||||||
NotAfter time.Time
|
|
||||||
Bundle bool
|
Bundle bool
|
||||||
PreferredChain string
|
PreferredChain string
|
||||||
|
|
||||||
// A string uniquely identifying the profile
|
|
||||||
// which will be used to affect issuance of the certificate requested by this Order.
|
|
||||||
// - https://www.ietf.org/id/draft-ietf-acme-profiles-00.html#section-4
|
|
||||||
Profile string
|
|
||||||
|
|
||||||
AlwaysDeactivateAuthorizations bool
|
|
||||||
|
|
||||||
// A string uniquely identifying a previously-issued certificate which this
|
|
||||||
// order is intended to replace.
|
|
||||||
// - https://www.rfc-editor.org/rfc/rfc9773.html#section-5
|
|
||||||
ReplacesCertID string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type resolver interface {
|
type resolver interface {
|
||||||
|
|
@ -124,8 +74,6 @@ type resolver interface {
|
||||||
type CertifierOptions struct {
|
type CertifierOptions struct {
|
||||||
KeyType certcrypto.KeyType
|
KeyType certcrypto.KeyType
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
OverallRequestLimit int
|
|
||||||
DisableCommonName bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Certifier A service to obtain/renew/revoke certificates.
|
// Certifier A service to obtain/renew/revoke certificates.
|
||||||
|
|
@ -133,23 +81,15 @@ type Certifier struct {
|
||||||
core *api.Core
|
core *api.Core
|
||||||
resolver resolver
|
resolver resolver
|
||||||
options CertifierOptions
|
options CertifierOptions
|
||||||
overallRequestLimit int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertifier creates a Certifier.
|
// NewCertifier creates a Certifier.
|
||||||
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
func NewCertifier(core *api.Core, resolver resolver, options CertifierOptions) *Certifier {
|
||||||
c := &Certifier{
|
return &Certifier{
|
||||||
core: core,
|
core: core,
|
||||||
resolver: resolver,
|
resolver: resolver,
|
||||||
options: options,
|
options: options,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.overallRequestLimit = options.OverallRequestLimit
|
|
||||||
if c.overallRequestLimit <= 0 {
|
|
||||||
c.overallRequestLimit = DefaultOverallRequestLimit
|
|
||||||
}
|
|
||||||
|
|
||||||
return c
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Obtain tries to obtain a single certificate using all domains passed into it.
|
// Obtain tries to obtain a single certificate using all domains passed into it.
|
||||||
|
|
@ -169,14 +109,7 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||||
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Obtaining SAN certificate", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
orderOpts := &api.OrderOptions{
|
order, err := c.core.Orders.New(domains)
|
||||||
NotBefore: request.NotBefore,
|
|
||||||
NotAfter: request.NotAfter,
|
|
||||||
Profile: request.Profile,
|
|
||||||
ReplacesCertID: request.ReplacesCertID,
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -184,33 +117,33 @@ func (c *Certifier) Obtain(request ObtainRequest) (*Resource, error) {
|
||||||
authz, err := c.getAuthorizations(order)
|
authz, err := c.getAuthorizations(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
c.deactivateAuthorizations(order)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.resolver.Solve(authz)
|
err = c.resolver.Solve(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
c.deactivateAuthorizations(order)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
failures := newObtainError()
|
failures := make(obtainError)
|
||||||
|
cert, err := c.getForOrder(domains, order, request.Bundle, request.PrivateKey, request.MustStaple, request.PreferredChain)
|
||||||
cert, err := c.getForOrder(domains, order, request)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, auth := range authz {
|
for _, auth := range authz {
|
||||||
failures.Add(challenge.GetTargetedDomain(auth), err)
|
failures[challenge.GetTargetedDomain(auth)] = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.AlwaysDeactivateAuthorizations {
|
// Do not return an empty failures map, because
|
||||||
c.deactivateAuthorizations(order, true)
|
// it would still be a non-nil error value
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return cert, failures
|
||||||
}
|
}
|
||||||
|
return cert, nil
|
||||||
return cert, failures.Join()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
// ObtainForCSR tries to obtain a certificate matching the CSR passed into it.
|
||||||
|
|
@ -237,14 +170,7 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
||||||
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Obtaining SAN certificate given a CSR", strings.Join(domains, ", "))
|
||||||
}
|
}
|
||||||
|
|
||||||
orderOpts := &api.OrderOptions{
|
order, err := c.core.Orders.New(domains)
|
||||||
NotBefore: request.NotBefore,
|
|
||||||
NotAfter: request.NotAfter,
|
|
||||||
Profile: request.Profile,
|
|
||||||
ReplacesCertID: request.ReplacesCertID,
|
|
||||||
}
|
|
||||||
|
|
||||||
order, err := c.core.Orders.NewWithOptions(domains, orderOpts)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -252,93 +178,72 @@ func (c *Certifier) ObtainForCSR(request ObtainForCSRRequest) (*Resource, error)
|
||||||
authz, err := c.getAuthorizations(order)
|
authz, err := c.getAuthorizations(order)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
c.deactivateAuthorizations(order)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = c.resolver.Solve(authz)
|
err = c.resolver.Solve(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If any challenge fails, return. Do not generate partial SAN certificates.
|
// If any challenge fails, return. Do not generate partial SAN certificates.
|
||||||
c.deactivateAuthorizations(order, request.AlwaysDeactivateAuthorizations)
|
c.deactivateAuthorizations(order)
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
log.Infof("[%s] acme: Validations succeeded; requesting certificates", strings.Join(domains, ", "))
|
||||||
|
|
||||||
failures := newObtainError()
|
failures := make(obtainError)
|
||||||
|
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, nil, request.PreferredChain)
|
||||||
var privateKey []byte
|
|
||||||
if request.PrivateKey != nil {
|
|
||||||
privateKey = certcrypto.PEMEncode(request.PrivateKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
cert, err := c.getForCSR(domains, order, request.Bundle, request.CSR.Raw, privateKey, request.PreferredChain)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
for _, auth := range authz {
|
for _, auth := range authz {
|
||||||
failures.Add(challenge.GetTargetedDomain(auth), err)
|
failures[challenge.GetTargetedDomain(auth)] = err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.AlwaysDeactivateAuthorizations {
|
|
||||||
c.deactivateAuthorizations(order, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
if cert != nil {
|
if cert != nil {
|
||||||
// Add the CSR to the certificate so that it can be used for renewals.
|
// Add the CSR to the certificate so that it can be used for renewals.
|
||||||
cert.CSR = certcrypto.PEMEncode(request.CSR)
|
cert.CSR = certcrypto.PEMEncode(request.CSR)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cert, failures.Join()
|
// Do not return an empty failures map,
|
||||||
|
// because it would still be a non-nil error value
|
||||||
|
if len(failures) > 0 {
|
||||||
|
return cert, failures
|
||||||
|
}
|
||||||
|
return cert, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, request ObtainRequest) (*Resource, error) {
|
func (c *Certifier) getForOrder(domains []string, order acme.ExtendedOrder, bundle bool, privateKey crypto.PrivateKey, mustStaple bool, preferredChain string) (*Resource, error) {
|
||||||
privateKey := request.PrivateKey
|
|
||||||
|
|
||||||
if privateKey == nil {
|
if privateKey == nil {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType)
|
privateKey, err = certcrypto.GeneratePrivateKey(c.options.KeyType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
commonName := ""
|
// Determine certificate name(s) based on the authorization resources
|
||||||
if len(domains[0]) <= 64 && !c.options.DisableCommonName {
|
commonName := domains[0]
|
||||||
commonName = domains[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
// RFC8555 Section 7.4 "Applying for Certificate Issuance"
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.4
|
// https://tools.ietf.org/html/rfc8555#section-7.4
|
||||||
// says:
|
// says:
|
||||||
// Clients SHOULD NOT make any assumptions about the sort order of
|
// Clients SHOULD NOT make any assumptions about the sort order of
|
||||||
// "identifiers" or "authorizations" elements in the returned order
|
// "identifiers" or "authorizations" elements in the returned order
|
||||||
// object.
|
// object.
|
||||||
|
san := []string{commonName}
|
||||||
var san []string
|
|
||||||
if commonName != "" {
|
|
||||||
san = append(san, commonName)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, auth := range order.Identifiers {
|
for _, auth := range order.Identifiers {
|
||||||
if auth.Value != commonName {
|
if auth.Value != commonName {
|
||||||
san = append(san, auth.Value)
|
san = append(san, auth.Value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
csrOptions := certcrypto.CSROptions{
|
// TODO: should the CSR be customizable?
|
||||||
Domain: commonName,
|
csr, err := certcrypto.GenerateCSR(privateKey, commonName, san, mustStaple)
|
||||||
SAN: san,
|
|
||||||
MustStaple: request.MustStaple,
|
|
||||||
EmailAddresses: request.EmailAddresses,
|
|
||||||
}
|
|
||||||
|
|
||||||
csr, err := certcrypto.CreateCSR(privateKey, csrOptions)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.getForCSR(domains, order, request.Bundle, csr, certcrypto.PEMEncode(privateKey), request.PreferredChain)
|
return c.getForCSR(domains, order, bundle, csr, certcrypto.PEMEncode(privateKey), preferredChain)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr, privateKeyPem []byte, preferredChain string) (*Resource, error) {
|
func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle bool, csr, privateKeyPem []byte, preferredChain string) (*Resource, error) {
|
||||||
|
|
@ -347,14 +252,15 @@ func (c *Certifier) getForCSR(domains []string, order acme.ExtendedOrder, bundle
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
commonName := domains[0]
|
||||||
certRes := &Resource{
|
certRes := &Resource{
|
||||||
Domain: domains[0],
|
Domain: commonName,
|
||||||
CertURL: respOrder.Certificate,
|
CertURL: respOrder.Certificate,
|
||||||
PrivateKey: privateKeyPem,
|
PrivateKey: privateKeyPem,
|
||||||
}
|
}
|
||||||
|
|
||||||
if respOrder.Status == acme.StatusValid {
|
if respOrder.Status == acme.StatusValid {
|
||||||
// if the certificate is available right away, shortcut!
|
// if the certificate is available right away, short cut!
|
||||||
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
|
ok, errR := c.checkResponse(respOrder, certRes, bundle, preferredChain)
|
||||||
if errR != nil {
|
if errR != nil {
|
||||||
return nil, errR
|
return nil, errR
|
||||||
|
|
@ -443,11 +349,6 @@ func (c *Certifier) checkResponse(order acme.ExtendedOrder, certRes *Resource, b
|
||||||
|
|
||||||
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
// Revoke takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
||||||
func (c *Certifier) Revoke(cert []byte) error {
|
func (c *Certifier) Revoke(cert []byte) error {
|
||||||
return c.RevokeWithReason(cert, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
// RevokeWithReason takes a PEM encoded certificate or bundle and tries to revoke it at the CA.
|
|
||||||
func (c *Certifier) RevokeWithReason(cert []byte, reason *uint) error {
|
|
||||||
certificates, err := certcrypto.ParsePEMBundle(cert)
|
certificates, err := certcrypto.ParsePEMBundle(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -460,28 +361,11 @@ func (c *Certifier) RevokeWithReason(cert []byte, reason *uint) error {
|
||||||
|
|
||||||
revokeMsg := acme.RevokeCertMessage{
|
revokeMsg := acme.RevokeCertMessage{
|
||||||
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
Certificate: base64.RawURLEncoding.EncodeToString(x509Cert.Raw),
|
||||||
Reason: reason,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.core.Certificates.Revoke(revokeMsg)
|
return c.core.Certificates.Revoke(revokeMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenewOptions options used by Certifier.RenewWithOptions.
|
|
||||||
type RenewOptions struct {
|
|
||||||
NotBefore time.Time
|
|
||||||
NotAfter time.Time
|
|
||||||
// If true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
|
||||||
Bundle bool
|
|
||||||
PreferredChain string
|
|
||||||
|
|
||||||
Profile string
|
|
||||||
|
|
||||||
AlwaysDeactivateAuthorizations bool
|
|
||||||
// Not supported for CSR request.
|
|
||||||
MustStaple bool
|
|
||||||
EmailAddresses []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Renew takes a Resource and tries to renew the certificate.
|
// Renew takes a Resource and tries to renew the certificate.
|
||||||
//
|
//
|
||||||
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
||||||
|
|
@ -492,27 +376,7 @@ type RenewOptions struct {
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
||||||
//
|
//
|
||||||
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
||||||
//
|
|
||||||
// Deprecated: use RenewWithOptions instead.
|
|
||||||
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) {
|
func (c *Certifier) Renew(certRes Resource, bundle, mustStaple bool, preferredChain string) (*Resource, error) {
|
||||||
return c.RenewWithOptions(certRes, &RenewOptions{
|
|
||||||
Bundle: bundle,
|
|
||||||
PreferredChain: preferredChain,
|
|
||||||
MustStaple: mustStaple,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewWithOptions takes a Resource and tries to renew the certificate.
|
|
||||||
//
|
|
||||||
// If the renewal process succeeds, the new certificate will be returned in a new CertResource.
|
|
||||||
// Please be aware that this function will return a new certificate in ANY case that is not an error.
|
|
||||||
// If the server does not provide us with a new cert on a GET request to the CertURL
|
|
||||||
// this function will start a new-cert flow where a new certificate gets generated.
|
|
||||||
//
|
|
||||||
// If bundle is true, the []byte contains both the issuer certificate and your issued certificate as a bundle.
|
|
||||||
//
|
|
||||||
// For private key reuse the PrivateKey property of the passed in Resource should be non-nil.
|
|
||||||
func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (*Resource, error) {
|
|
||||||
// Input certificate is PEM encoded.
|
// Input certificate is PEM encoded.
|
||||||
// Decode it here as we may need the decoded cert later on in the renewal process.
|
// Decode it here as we may need the decoded cert later on in the renewal process.
|
||||||
// The input may be a bundle or a single certificate.
|
// The input may be a bundle or a single certificate.
|
||||||
|
|
@ -539,18 +403,11 @@ func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (*
|
||||||
return nil, errP
|
return nil, errP
|
||||||
}
|
}
|
||||||
|
|
||||||
request := ObtainForCSRRequest{CSR: csr}
|
return c.ObtainForCSR(ObtainForCSRRequest{
|
||||||
|
CSR: csr,
|
||||||
if options != nil {
|
Bundle: bundle,
|
||||||
request.NotBefore = options.NotBefore
|
PreferredChain: preferredChain,
|
||||||
request.NotAfter = options.NotAfter
|
})
|
||||||
request.Bundle = options.Bundle
|
|
||||||
request.PreferredChain = options.PreferredChain
|
|
||||||
request.Profile = options.Profile
|
|
||||||
request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.ObtainForCSR(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var privateKey crypto.PrivateKey
|
var privateKey crypto.PrivateKey
|
||||||
|
|
@ -561,23 +418,13 @@ func (c *Certifier) RenewWithOptions(certRes Resource, options *RenewOptions) (*
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
request := ObtainRequest{
|
query := ObtainRequest{
|
||||||
Domains: certcrypto.ExtractDomains(x509Cert),
|
Domains: certcrypto.ExtractDomains(x509Cert),
|
||||||
|
Bundle: bundle,
|
||||||
PrivateKey: privateKey,
|
PrivateKey: privateKey,
|
||||||
|
MustStaple: mustStaple,
|
||||||
}
|
}
|
||||||
|
return c.Obtain(query)
|
||||||
if options != nil {
|
|
||||||
request.MustStaple = options.MustStaple
|
|
||||||
request.NotBefore = options.NotBefore
|
|
||||||
request.NotAfter = options.NotAfter
|
|
||||||
request.Bundle = options.Bundle
|
|
||||||
request.PreferredChain = options.PreferredChain
|
|
||||||
request.EmailAddresses = options.EmailAddresses
|
|
||||||
request.Profile = options.Profile
|
|
||||||
request.AlwaysDeactivateAuthorizations = options.AlwaysDeactivateAuthorizations
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Obtain(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
// GetOCSP takes a PEM encoded cert or cert bundle returning the raw OCSP response,
|
||||||
|
|
@ -618,7 +465,7 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
issuerBytes, errC := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
issuerBytes, errC := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
if errC != nil {
|
if errC != nil {
|
||||||
return nil, nil, errC
|
return nil, nil, errC
|
||||||
}
|
}
|
||||||
|
|
@ -647,7 +494,7 @@ func (c *Certifier) GetOCSP(bundle []byte) ([]byte, *ocsp.Response, error) {
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
ocspResBytes, err := io.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
ocspResBytes, err := ioutil.ReadAll(http.MaxBytesReader(nil, resp.Body, maxBodySize))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -678,13 +525,8 @@ func (c *Certifier) Get(url string, bundle bool) (*Resource, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
domain, err := certcrypto.GetCertificateMainDomain(x509Certs[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Resource{
|
return &Resource{
|
||||||
Domain: domain,
|
Domain: x509Certs[0].Subject.CommonName,
|
||||||
Certificate: cert,
|
Certificate: cert,
|
||||||
IssuerCertificate: issuer,
|
IssuerCertificate: issuer,
|
||||||
CertURL: url,
|
CertURL: url,
|
||||||
|
|
@ -712,20 +554,19 @@ func checkOrderStatus(order acme.ExtendedOrder) (bool, error) {
|
||||||
case acme.StatusValid:
|
case acme.StatusValid:
|
||||||
return true, nil
|
return true, nil
|
||||||
case acme.StatusInvalid:
|
case acme.StatusInvalid:
|
||||||
return false, fmt.Errorf("invalid order: %w", order.Err())
|
return false, order.Error
|
||||||
default:
|
default:
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc8555.html#section-7.1.4
|
// https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||||
// The domain name MUST be encoded in the form in which it would appear in a certificate.
|
// The domain name MUST be encoded in the form in which it would appear in a certificate.
|
||||||
// That is, it MUST be encoded according to the rules in Section 7 of [RFC5280].
|
// That is, it MUST be encoded according to the rules in Section 7 of [RFC5280].
|
||||||
//
|
//
|
||||||
// https://www.rfc-editor.org/rfc/rfc5280.html#section-7
|
// https://tools.ietf.org/html/rfc5280#section-7
|
||||||
func sanitizeDomain(domains []string) []string {
|
func sanitizeDomain(domains []string) []string {
|
||||||
var sanitizedDomains []string
|
var sanitizedDomains []string
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
sanitizedDomain, err := idna.ToASCII(domain)
|
sanitizedDomain, err := idna.ToASCII(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -734,6 +575,5 @@ func sanitizeDomain(domains []string) []string {
|
||||||
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
|
sanitizedDomains = append(sanitizedDomains, sanitizedDomain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sanitizedDomains
|
return sanitizedDomains
|
||||||
}
|
}
|
||||||
|
|
|
||||||
36
vendor/github.com/go-acme/lego/v4/certificate/errors.go
generated
vendored
36
vendor/github.com/go-acme/lego/v4/certificate/errors.go
generated
vendored
|
|
@ -1,37 +1,27 @@
|
||||||
package certificate
|
package certificate
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"sort"
|
||||||
)
|
)
|
||||||
|
|
||||||
type obtainError struct {
|
// obtainError is returned when there are specific errors available per domain.
|
||||||
data map[string]error
|
type obtainError map[string]error
|
||||||
}
|
|
||||||
|
|
||||||
func newObtainError() *obtainError {
|
func (e obtainError) Error() string {
|
||||||
return &obtainError{data: make(map[string]error)}
|
buffer := bytes.NewBufferString("error: one or more domains had a problem:\n")
|
||||||
}
|
|
||||||
|
|
||||||
func (e *obtainError) Add(domain string, err error) {
|
var domains []string
|
||||||
e.data[domain] = err
|
for domain := range e {
|
||||||
}
|
domains = append(domains, domain)
|
||||||
|
|
||||||
func (e *obtainError) Join() error {
|
|
||||||
if e == nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
sort.Strings(domains)
|
||||||
|
|
||||||
if len(e.data) == 0 {
|
for _, domain := range domains {
|
||||||
return nil
|
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||||
}
|
}
|
||||||
|
return buffer.String()
|
||||||
var err error
|
|
||||||
for d, e := range e.data {
|
|
||||||
err = errors.Join(err, fmt.Errorf("%s: %w", d, e))
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("error: one or more domains had a problem:\n%w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type domainError struct {
|
type domainError struct {
|
||||||
|
|
|
||||||
132
vendor/github.com/go-acme/lego/v4/certificate/renewal.go
generated
vendored
132
vendor/github.com/go-acme/lego/v4/certificate/renewal.go
generated
vendored
|
|
@ -1,132 +0,0 @@
|
||||||
package certificate
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/x509"
|
|
||||||
"encoding/asn1"
|
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
|
||||||
)
|
|
||||||
|
|
||||||
// RenewalInfoRequest contains the necessary renewal information.
|
|
||||||
type RenewalInfoRequest struct {
|
|
||||||
Cert *x509.Certificate
|
|
||||||
}
|
|
||||||
|
|
||||||
// RenewalInfoResponse is a wrapper around acme.RenewalInfoResponse that provides a method for determining when to renew a certificate.
|
|
||||||
type RenewalInfoResponse struct {
|
|
||||||
acme.RenewalInfoResponse
|
|
||||||
|
|
||||||
// RetryAfter header indicating the polling interval that the ACME server recommends.
|
|
||||||
// Conforming clients SHOULD query the renewalInfo URL again after the RetryAfter period has passed,
|
|
||||||
// as the server may provide a different suggestedWindow.
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9773.html#section-4.2
|
|
||||||
RetryAfter time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
// ShouldRenewAt determines the optimal renewal time based on the current time (UTC),renewal window suggest by ARI, and the client's willingness to sleep.
|
|
||||||
// It returns a pointer to a time.Time value indicating when the renewal should be attempted or nil if deferred until the next normal wake time.
|
|
||||||
// This method implements the RECOMMENDED algorithm described in RFC 9773.
|
|
||||||
//
|
|
||||||
// - (4.1-11. Getting Renewal Information) https://www.rfc-editor.org/rfc/rfc9773.html
|
|
||||||
func (r *RenewalInfoResponse) ShouldRenewAt(now time.Time, willingToSleep time.Duration) *time.Time {
|
|
||||||
// Explicitly convert all times to UTC.
|
|
||||||
now = now.UTC()
|
|
||||||
start := r.SuggestedWindow.Start.UTC()
|
|
||||||
end := r.SuggestedWindow.End.UTC()
|
|
||||||
|
|
||||||
// Select a uniform random time within the suggested window.
|
|
||||||
rt := start
|
|
||||||
if window := end.Sub(start); window > 0 {
|
|
||||||
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
|
||||||
rt = rt.Add(randomDuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the selected time is in the past, attempt renewal immediately.
|
|
||||||
if rt.Before(now) {
|
|
||||||
return &now
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, if the client can schedule itself to attempt renewal at exactly the selected time, do so.
|
|
||||||
willingToSleepUntil := now.Add(willingToSleep)
|
|
||||||
if willingToSleepUntil.After(rt) || willingToSleepUntil.Equal(rt) {
|
|
||||||
return &rt
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Otherwise, if the selected time is before the next time that the client would wake up normally, attempt renewal immediately.
|
|
||||||
|
|
||||||
// Otherwise, sleep until the next normal wake time, re-check ARI, and return to Step 1.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetRenewalInfo sends a request to the ACME server's renewalInfo endpoint to obtain a suggested renewal window.
|
|
||||||
// The caller MUST provide the certificate and issuer certificate for the certificate they wish to renew.
|
|
||||||
// The caller should attempt to renew the certificate at the time indicated by the ShouldRenewAt method of the returned RenewalInfoResponse object.
|
|
||||||
//
|
|
||||||
// Note: this endpoint is part of a draft specification, not all ACME servers will implement it.
|
|
||||||
// This method will return api.ErrNoARI if the server does not advertise a renewal info endpoint.
|
|
||||||
//
|
|
||||||
// https://www.rfc-editor.org/rfc/rfc9773.html
|
|
||||||
func (c *Certifier) GetRenewalInfo(req RenewalInfoRequest) (*RenewalInfoResponse, error) {
|
|
||||||
certID, err := MakeARICertID(req.Cert)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error making certID: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resp, err := c.core.Certificates.GetRenewalInfo(certID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
var info RenewalInfoResponse
|
|
||||||
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&info)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if retry := resp.Header.Get("Retry-After"); retry != "" {
|
|
||||||
info.RetryAfter, err = time.ParseDuration(retry + "s")
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &info, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeARICertID constructs a certificate identifier as described in RFC 9773, section 4.1.
|
|
||||||
func MakeARICertID(leaf *x509.Certificate) (string, error) {
|
|
||||||
if leaf == nil {
|
|
||||||
return "", errors.New("leaf certificate is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal the Serial Number into DER.
|
|
||||||
der, err := asn1.Marshal(leaf.SerialNumber)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the DER encoded bytes are sufficient (at least 3 bytes: tag,
|
|
||||||
// length, and value).
|
|
||||||
if len(der) < 3 {
|
|
||||||
return "", errors.New("invalid DER encoding of serial number")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract only the integer bytes from the DER encoded Serial Number
|
|
||||||
// Skipping the first 2 bytes (tag and length).
|
|
||||||
serial := base64.RawURLEncoding.EncodeToString(der[2:])
|
|
||||||
|
|
||||||
// Convert the Authority Key Identifier to base64url encoding without
|
|
||||||
// padding.
|
|
||||||
aki := base64.RawURLEncoding.EncodeToString(leaf.AuthorityKeyId)
|
|
||||||
|
|
||||||
// Construct the final identifier by concatenating AKI and Serial Number.
|
|
||||||
return fmt.Sprintf("%s.%s", aki, serial), nil
|
|
||||||
}
|
|
||||||
7
vendor/github.com/go-acme/lego/v4/challenge/challenges.go
generated
vendored
7
vendor/github.com/go-acme/lego/v4/challenge/challenges.go
generated
vendored
|
|
@ -10,15 +10,15 @@ import (
|
||||||
type Type string
|
type Type string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// HTTP01 is the "http-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8555.html#section-8.3
|
// HTTP01 is the "http-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.3
|
||||||
// Note: ChallengePath returns the URL path to fulfill this challenge.
|
// Note: ChallengePath returns the URL path to fulfill this challenge.
|
||||||
HTTP01 = Type("http-01")
|
HTTP01 = Type("http-01")
|
||||||
|
|
||||||
// DNS01 is the "dns-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8555.html#section-8.4
|
// DNS01 is the "dns-01" ACME challenge https://tools.ietf.org/html/rfc8555#section-8.4
|
||||||
// Note: GetRecord returns a DNS record which will fulfill this challenge.
|
// Note: GetRecord returns a DNS record which will fulfill this challenge.
|
||||||
DNS01 = Type("dns-01")
|
DNS01 = Type("dns-01")
|
||||||
|
|
||||||
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://www.rfc-editor.org/rfc/rfc8737.html
|
// TLSALPN01 is the "tls-alpn-01" ACME challenge https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07
|
||||||
TLSALPN01 = Type("tls-alpn-01")
|
TLSALPN01 = Type("tls-alpn-01")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -40,6 +40,5 @@ func GetTargetedDomain(authz acme.Authorization) string {
|
||||||
if authz.Wildcard {
|
if authz.Wildcard {
|
||||||
return "*." + authz.Identifier.Value
|
return "*." + authz.Identifier.Value
|
||||||
}
|
}
|
||||||
|
|
||||||
return authz.Identifier.Value
|
return authz.Identifier.Value
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/cname.go
generated
vendored
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/cname.go
generated
vendored
|
|
@ -1,16 +1,12 @@
|
||||||
package dns01
|
package dns01
|
||||||
|
|
||||||
import (
|
import "github.com/miekg/dns"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Update FQDN with CNAME if any.
|
// Update FQDN with CNAME if any.
|
||||||
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
|
func updateDomainWithCName(r *dns.Msg, fqdn string) string {
|
||||||
for _, rr := range r.Answer {
|
for _, rr := range r.Answer {
|
||||||
if cn, ok := rr.(*dns.CNAME); ok {
|
if cn, ok := rr.(*dns.CNAME); ok {
|
||||||
if strings.EqualFold(cn.Hdr.Name, fqdn) {
|
if cn.Hdr.Name == fqdn {
|
||||||
return cn.Target
|
return cn.Target
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
77
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge.go
generated
vendored
77
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge.go
generated
vendored
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
|
|
@ -40,7 +39,6 @@ func CondOption(condition bool, opt ChallengeOption) ChallengeOption {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return opt
|
return opt
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,10 +114,9 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
info := GetChallengeInfo(authz.Identifier.Value, keyAuth)
|
fqdn, value := GetRecord(authz.Identifier.Value, keyAuth)
|
||||||
|
|
||||||
var timeout, interval time.Duration
|
var timeout, interval time.Duration
|
||||||
|
|
||||||
switch provider := c.provider.(type) {
|
switch provider := c.provider.(type) {
|
||||||
case challenge.ProviderTimeout:
|
case challenge.ProviderTimeout:
|
||||||
timeout, interval = provider.Timeout()
|
timeout, interval = provider.Timeout()
|
||||||
|
|
@ -127,16 +124,15 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
timeout, interval = DefaultPropagationTimeout, DefaultPollingInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Checking DNS record propagation. [nameservers=%s]", domain, strings.Join(recursiveNameservers, ","))
|
log.Infof("[%s] acme: Checking DNS record propagation using %+v", domain, recursiveNameservers)
|
||||||
|
|
||||||
time.Sleep(interval)
|
time.Sleep(interval)
|
||||||
|
|
||||||
err = wait.For("propagation", timeout, interval, func() (bool, error) {
|
err = wait.For("propagation", timeout, interval, func() (bool, error) {
|
||||||
stop, errP := c.preCheck.call(domain, info.EffectiveFQDN, info.Value)
|
stop, errP := c.preCheck.call(domain, fqdn, value)
|
||||||
if !stop || errP != nil {
|
if !stop || errP != nil {
|
||||||
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
|
log.Infof("[%s] acme: Waiting for DNS record propagation.", domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
return stop, errP
|
return stop, errP
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -144,7 +140,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
chlng.KeyAuthorization = keyAuth
|
chlng.KeyAuthorization = keyAuth
|
||||||
|
|
||||||
return c.validate(c.core, domain, chlng)
|
return c.validate(c.core, domain, chlng)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -169,7 +164,6 @@ func (c *Challenge) Sequential() (bool, time.Duration) {
|
||||||
if p, ok := c.provider.(sequential); ok {
|
if p, ok := c.provider.(sequential); ok {
|
||||||
return ok, p.Sequential()
|
return ok, p.Sequential()
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, 0
|
return false, 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,68 +172,19 @@ type sequential interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge.
|
// GetRecord returns a DNS record which will fulfill the `dns-01` challenge.
|
||||||
//
|
|
||||||
// Deprecated: use GetChallengeInfo instead.
|
|
||||||
func GetRecord(domain, keyAuth string) (fqdn, value string) {
|
func GetRecord(domain, keyAuth string) (fqdn, value string) {
|
||||||
info := GetChallengeInfo(domain, keyAuth)
|
|
||||||
|
|
||||||
return info.EffectiveFQDN, info.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChallengeInfo contains the information use to create the TXT record.
|
|
||||||
type ChallengeInfo struct {
|
|
||||||
// FQDN is the full-qualified challenge domain (i.e. `_acme-challenge.[domain].`)
|
|
||||||
FQDN string
|
|
||||||
|
|
||||||
// EffectiveFQDN contains the resulting FQDN after the CNAMEs resolutions.
|
|
||||||
EffectiveFQDN string
|
|
||||||
|
|
||||||
// Value contains the value for the TXT record.
|
|
||||||
Value string
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetChallengeInfo returns information used to create a DNS record which will fulfill the `dns-01` challenge.
|
|
||||||
func GetChallengeInfo(domain, keyAuth string) ChallengeInfo {
|
|
||||||
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
keyAuthShaBytes := sha256.Sum256([]byte(keyAuth))
|
||||||
// base64URL encoding without padding
|
// base64URL encoding without padding
|
||||||
value := base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
value = base64.RawURLEncoding.EncodeToString(keyAuthShaBytes[:sha256.Size])
|
||||||
|
fqdn = fmt.Sprintf("_acme-challenge.%s.", domain)
|
||||||
|
|
||||||
ok, _ := strconv.ParseBool(os.Getenv("LEGO_DISABLE_CNAME_SUPPORT"))
|
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_CNAME_SUPPORT")); ok {
|
||||||
|
|
||||||
return ChallengeInfo{
|
|
||||||
Value: value,
|
|
||||||
FQDN: getChallengeFQDN(domain, false),
|
|
||||||
EffectiveFQDN: getChallengeFQDN(domain, !ok),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func getChallengeFQDN(domain string, followCNAME bool) string {
|
|
||||||
fqdn := fmt.Sprintf("_acme-challenge.%s.", domain)
|
|
||||||
|
|
||||||
if !followCNAME {
|
|
||||||
return fqdn
|
|
||||||
}
|
|
||||||
|
|
||||||
// recursion counter so it doesn't spin out of control
|
|
||||||
for range 50 {
|
|
||||||
// Keep following CNAMEs
|
|
||||||
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
r, err := dnsQuery(fqdn, dns.TypeCNAME, recursiveNameservers, true)
|
||||||
|
// Check if the domain has CNAME then return that
|
||||||
if err != nil || r.Rcode != dns.RcodeSuccess {
|
if err == nil && r.Rcode == dns.RcodeSuccess {
|
||||||
// No more CNAME records to follow, exit
|
fqdn = updateDomainWithCName(r, fqdn)
|
||||||
break
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the domain has CNAME then use that
|
return
|
||||||
cname := updateDomainWithCName(r, fqdn)
|
|
||||||
if cname == fqdn {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("Found CNAME entry for %q: %q", fqdn, cname)
|
|
||||||
|
|
||||||
fqdn = cname
|
|
||||||
}
|
|
||||||
|
|
||||||
return fqdn
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
23
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge_manual.go
generated
vendored
23
vendor/github.com/go-acme/lego/v4/challenge/dns01/dns_challenge_manual.go
generated
vendored
|
|
@ -8,7 +8,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
dnsTemplate = `%s %d IN TXT %q`
|
dnsTemplate = `%s %d IN TXT "%s"`
|
||||||
)
|
)
|
||||||
|
|
||||||
// DNSProviderManual is an implementation of the ChallengeProvider interface.
|
// DNSProviderManual is an implementation of the ChallengeProvider interface.
|
||||||
|
|
@ -21,36 +21,33 @@ func NewDNSProviderManual() (*DNSProviderManual, error) {
|
||||||
|
|
||||||
// Present prints instructions for manually creating the TXT record.
|
// Present prints instructions for manually creating the TXT record.
|
||||||
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
func (*DNSProviderManual) Present(domain, token, keyAuth string) error {
|
||||||
info := GetChallengeInfo(domain, keyAuth)
|
fqdn, value := GetRecord(domain, keyAuth)
|
||||||
|
|
||||||
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
authZone, err := FindZoneByFqdn(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("manual: could not find zone: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
fmt.Printf("lego: Please create the following TXT record in your %s zone:\n", authZone)
|
||||||
fmt.Printf(dnsTemplate+"\n", info.EffectiveFQDN, DefaultTTL, info.Value)
|
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, value)
|
||||||
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
fmt.Printf("lego: Press 'Enter' when you are done\n")
|
||||||
|
|
||||||
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
_, err = bufio.NewReader(os.Stdin).ReadBytes('\n')
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("manual: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp prints instructions for manually removing the TXT record.
|
// CleanUp prints instructions for manually removing the TXT record.
|
||||||
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
func (*DNSProviderManual) CleanUp(domain, token, keyAuth string) error {
|
||||||
info := GetChallengeInfo(domain, keyAuth)
|
fqdn, _ := GetRecord(domain, keyAuth)
|
||||||
|
|
||||||
authZone, err := FindZoneByFqdn(info.EffectiveFQDN)
|
authZone, err := FindZoneByFqdn(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("manual: could not find zone: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
fmt.Printf("lego: You can now remove this TXT record from your %s zone:\n", authZone)
|
||||||
fmt.Printf(dnsTemplate+"\n", info.EffectiveFQDN, DefaultTTL, "...")
|
fmt.Printf(dnsTemplate+"\n", fqdn, DefaultTTL, "...")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
24
vendor/github.com/go-acme/lego/v4/challenge/dns01/domain.go
generated
vendored
24
vendor/github.com/go-acme/lego/v4/challenge/dns01/domain.go
generated
vendored
|
|
@ -1,24 +0,0 @@
|
||||||
package dns01
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ExtractSubDomain extracts the subdomain part from a domain and a zone.
|
|
||||||
func ExtractSubDomain(domain, zone string) (string, error) {
|
|
||||||
canonDomain := dns.Fqdn(domain)
|
|
||||||
canonZone := dns.Fqdn(zone)
|
|
||||||
|
|
||||||
if canonDomain == canonZone {
|
|
||||||
return "", fmt.Errorf("no subdomain because the domain and the zone are identical: %s", canonDomain)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dns.IsSubDomain(canonZone, canonDomain) {
|
|
||||||
return "", fmt.Errorf("%s is not a subdomain of %s", canonDomain, canonZone)
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.TrimSuffix(canonDomain, "."+canonZone), nil
|
|
||||||
}
|
|
||||||
45
vendor/github.com/go-acme/lego/v4/challenge/dns01/fqdn.go
generated
vendored
45
vendor/github.com/go-acme/lego/v4/challenge/dns01/fqdn.go
generated
vendored
|
|
@ -1,16 +1,12 @@
|
||||||
package dns01
|
package dns01
|
||||||
|
|
||||||
import (
|
|
||||||
"iter"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// ToFqdn converts the name into a fqdn appending a trailing dot.
|
// ToFqdn converts the name into a fqdn appending a trailing dot.
|
||||||
//
|
|
||||||
// Deprecated: Use [github.com/miekg/dns.Fqdn] directly.
|
|
||||||
func ToFqdn(name string) string {
|
func ToFqdn(name string) string {
|
||||||
return dns.Fqdn(name)
|
n := len(name)
|
||||||
|
if n == 0 || name[n-1] == '.' {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return name + "."
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnFqdn converts the fqdn into a name removing the trailing dot.
|
// UnFqdn converts the fqdn into a name removing the trailing dot.
|
||||||
|
|
@ -19,36 +15,5 @@ func UnFqdn(name string) string {
|
||||||
if n != 0 && name[n-1] == '.' {
|
if n != 0 && name[n-1] == '.' {
|
||||||
return name[:n-1]
|
return name[:n-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
return name
|
return name
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnFqdnDomainsSeq generates a sequence of "unFQDNed" domain names derived from a domain (FQDN or not) in descending order.
|
|
||||||
func UnFqdnDomainsSeq(fqdn string) iter.Seq[string] {
|
|
||||||
return func(yield func(string) bool) {
|
|
||||||
if fqdn == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, index := range dns.Split(fqdn) {
|
|
||||||
if !yield(UnFqdn(fqdn[index:])) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DomainsSeq generates a sequence of domain names derived from a domain (FQDN or not) in descending order.
|
|
||||||
func DomainsSeq(fqdn string) iter.Seq[string] {
|
|
||||||
return func(yield func(string) bool) {
|
|
||||||
if fqdn == "" {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, index := range dns.Split(fqdn) {
|
|
||||||
if !yield(fqdn[index:]) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
200
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver.go
generated
vendored
200
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver.go
generated
vendored
|
|
@ -4,9 +4,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"slices"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
@ -16,7 +13,13 @@ import (
|
||||||
|
|
||||||
const defaultResolvConf = "/etc/resolv.conf"
|
const defaultResolvConf = "/etc/resolv.conf"
|
||||||
|
|
||||||
var fqdnSoaCache = &sync.Map{}
|
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
|
||||||
|
var dnsTimeout = 10 * time.Second
|
||||||
|
|
||||||
|
var (
|
||||||
|
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||||
|
muFqdnSoaCache sync.Mutex
|
||||||
|
)
|
||||||
|
|
||||||
var defaultNameservers = []string{
|
var defaultNameservers = []string{
|
||||||
"google-public-dns-a.google.com:53",
|
"google-public-dns-a.google.com:53",
|
||||||
|
|
@ -48,11 +51,9 @@ func (cache *soaCacheEntry) isExpired() bool {
|
||||||
|
|
||||||
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
// ClearFqdnCache clears the cache of fqdn to zone mappings. Primarily used in testing.
|
||||||
func ClearFqdnCache() {
|
func ClearFqdnCache() {
|
||||||
// TODO(ldez): use `fqdnSoaCache.Clear()` when updating to go1.23
|
muFqdnSoaCache.Lock()
|
||||||
fqdnSoaCache.Range(func(k, v any) bool {
|
fqdnSoaCache = map[string]*soaCacheEntry{}
|
||||||
fqdnSoaCache.Delete(k)
|
muFqdnSoaCache.Unlock()
|
||||||
return true
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
func AddDNSTimeout(timeout time.Duration) ChallengeOption {
|
||||||
|
|
@ -81,7 +82,6 @@ func getNameservers(path string, defaults []string) []string {
|
||||||
|
|
||||||
func ParseNameservers(servers []string) []string {
|
func ParseNameservers(servers []string) []string {
|
||||||
var resolvers []string
|
var resolvers []string
|
||||||
|
|
||||||
for _, resolver := range servers {
|
for _, resolver := range servers {
|
||||||
// ensure all servers have a port number
|
// ensure all servers have a port number
|
||||||
if _, _, err := net.SplitHostPort(resolver); err != nil {
|
if _, _, err := net.SplitHostPort(resolver); err != nil {
|
||||||
|
|
@ -90,7 +90,6 @@ func ParseNameservers(servers []string) []string {
|
||||||
resolvers = append(resolvers, resolver)
|
resolvers = append(resolvers, resolver)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return resolvers
|
return resolvers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -100,12 +99,12 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
|
|
||||||
zone, err := FindZoneByFqdn(fqdn)
|
zone, err := FindZoneByFqdn(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not find zone: %w", err)
|
return nil, fmt.Errorf("could not determine the zone: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
r, err := dnsQuery(zone, dns.TypeNS, recursiveNameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("NS call failed: %w", err)
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, rr := range r.Answer {
|
for _, rr := range r.Answer {
|
||||||
|
|
@ -117,8 +116,7 @@ func lookupNameservers(fqdn string) ([]string, error) {
|
||||||
if len(authoritativeNss) > 0 {
|
if len(authoritativeNss) > 0 {
|
||||||
return authoritativeNss, nil
|
return authoritativeNss, nil
|
||||||
}
|
}
|
||||||
|
return nil, errors.New("could not determine authoritative nameservers")
|
||||||
return nil, fmt.Errorf("[zone=%s] could not determine authoritative nameservers", zone)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
|
// FindPrimaryNsByFqdn determines the primary nameserver of the zone apex for the given fqdn
|
||||||
|
|
@ -132,9 +130,8 @@ func FindPrimaryNsByFqdn(fqdn string) (string, error) {
|
||||||
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
func FindPrimaryNsByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return soa.primaryNs, nil
|
return soa.primaryNs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,62 +146,60 @@ func FindZoneByFqdn(fqdn string) (string, error) {
|
||||||
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
func FindZoneByFqdnCustom(fqdn string, nameservers []string) (string, error) {
|
||||||
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
soa, err := lookupSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("[fqdn=%s] %w", fqdn, err)
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return soa.zone, nil
|
return soa.zone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
func lookupSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
|
muFqdnSoaCache.Lock()
|
||||||
|
defer muFqdnSoaCache.Unlock()
|
||||||
|
|
||||||
// Do we have it cached and is it still fresh?
|
// Do we have it cached and is it still fresh?
|
||||||
entAny, ok := fqdnSoaCache.Load(fqdn)
|
if ent := fqdnSoaCache[fqdn]; ent != nil && !ent.isExpired() {
|
||||||
if ok && entAny != nil {
|
|
||||||
ent, ok1 := entAny.(*soaCacheEntry)
|
|
||||||
if ok1 && !ent.isExpired() {
|
|
||||||
return ent, nil
|
return ent, nil
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
ent, err := fetchSoaByFqdn(fqdn, nameservers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fqdnSoaCache.Store(fqdn, ent)
|
fqdnSoaCache[fqdn] = ent
|
||||||
|
|
||||||
return ent, nil
|
return ent, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
var (
|
var err error
|
||||||
err error
|
var in *dns.Msg
|
||||||
r *dns.Msg
|
|
||||||
)
|
|
||||||
|
|
||||||
for domain := range DomainsSeq(fqdn) {
|
labelIndexes := dns.Split(fqdn)
|
||||||
r, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
for _, index := range labelIndexes {
|
||||||
|
domain := fqdn[index:]
|
||||||
|
|
||||||
|
in, err = dnsQuery(domain, dns.TypeSOA, nameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if r == nil {
|
if in == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
switch r.Rcode {
|
switch in.Rcode {
|
||||||
case dns.RcodeSuccess:
|
case dns.RcodeSuccess:
|
||||||
// Check if we got a SOA RR in the answer section
|
// Check if we got a SOA RR in the answer section
|
||||||
if len(r.Answer) == 0 {
|
if len(in.Answer) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// CNAME records cannot/should not exist at the root of a zone.
|
// CNAME records cannot/should not exist at the root of a zone.
|
||||||
// So we skip a domain when a CNAME is found.
|
// So we skip a domain when a CNAME is found.
|
||||||
if dnsMsgContainsCNAME(r) {
|
if dnsMsgContainsCNAME(in) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, ans := range r.Answer {
|
for _, ans := range in.Answer {
|
||||||
if soa, ok := ans.(*dns.SOA); ok {
|
if soa, ok := ans.(*dns.SOA); ok {
|
||||||
return newSoaCacheEntry(soa), nil
|
return newSoaCacheEntry(soa), nil
|
||||||
}
|
}
|
||||||
|
|
@ -213,48 +208,36 @@ func fetchSoaByFqdn(fqdn string, nameservers []string) (*soaCacheEntry, error) {
|
||||||
// NXDOMAIN
|
// NXDOMAIN
|
||||||
default:
|
default:
|
||||||
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
// Any response code other than NOERROR and NXDOMAIN is treated as error
|
||||||
return nil, &DNSError{Message: fmt.Sprintf("unexpected response for '%s'", domain), MsgOut: r}
|
return nil, fmt.Errorf("unexpected response code '%s' for %s", dns.RcodeToString[in.Rcode], domain)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, &DNSError{Message: fmt.Sprintf("could not find the start of authority for '%s'", fqdn), MsgOut: r, Err: err}
|
return nil, fmt.Errorf("could not find the start of authority for %s%s", fqdn, formatDNSError(in, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
|
// dnsMsgContainsCNAME checks for a CNAME answer in msg.
|
||||||
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
func dnsMsgContainsCNAME(msg *dns.Msg) bool {
|
||||||
return slices.ContainsFunc(msg.Answer, func(rr dns.RR) bool {
|
for _, ans := range msg.Answer {
|
||||||
_, ok := rr.(*dns.CNAME)
|
if _, ok := ans.(*dns.CNAME); ok {
|
||||||
return ok
|
return true
|
||||||
})
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
func dnsQuery(fqdn string, rtype uint16, nameservers []string, recursive bool) (*dns.Msg, error) {
|
||||||
m := createDNSMsg(fqdn, rtype, recursive)
|
m := createDNSMsg(fqdn, rtype, recursive)
|
||||||
|
|
||||||
if len(nameservers) == 0 {
|
var in *dns.Msg
|
||||||
return nil, &DNSError{Message: "empty list of nameservers"}
|
var err error
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
r *dns.Msg
|
|
||||||
err error
|
|
||||||
errAll error
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, ns := range nameservers {
|
for _, ns := range nameservers {
|
||||||
r, err = sendDNSQuery(m, ns)
|
in, err = sendDNSQuery(m, ns)
|
||||||
if err == nil && len(r.Answer) > 0 {
|
if err == nil && len(in.Answer) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
errAll = errors.Join(errAll, err)
|
|
||||||
}
|
}
|
||||||
|
return in, err
|
||||||
if err != nil {
|
|
||||||
return r, errAll
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||||
|
|
@ -270,85 +253,32 @@ func createDNSMsg(fqdn string, rtype uint16, recursive bool) *dns.Msg {
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
func sendDNSQuery(m *dns.Msg, ns string) (*dns.Msg, error) {
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv("LEGO_EXPERIMENTAL_DNS_TCP_ONLY")); ok {
|
|
||||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
|
||||||
|
|
||||||
r, _, err := tcp.Exchange(m, ns)
|
|
||||||
if err != nil {
|
|
||||||
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
udp := &dns.Client{Net: "udp", Timeout: dnsTimeout}
|
||||||
r, _, err := udp.Exchange(m, ns)
|
in, _, err := udp.Exchange(m, ns)
|
||||||
|
|
||||||
if r != nil && r.Truncated {
|
if in != nil && in.Truncated {
|
||||||
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
tcp := &dns.Client{Net: "tcp", Timeout: dnsTimeout}
|
||||||
// If the TCP request succeeds, the "err" will reset to nil
|
// If the TCP request succeeds, the err will reset to nil
|
||||||
r, _, err = tcp.Exchange(m, ns)
|
in, _, err = tcp.Exchange(m, ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
return in, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func formatDNSError(msg *dns.Msg, err error) string {
|
||||||
|
var parts []string
|
||||||
|
|
||||||
|
if msg != nil {
|
||||||
|
parts = append(parts, dns.RcodeToString[msg.Rcode])
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r, &DNSError{Message: "DNS call error", MsgIn: m, NS: ns, Err: err}
|
parts = append(parts, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return r, nil
|
if len(parts) > 0 {
|
||||||
}
|
return ": " + strings.Join(parts, " ")
|
||||||
|
}
|
||||||
// DNSError error related to DNS calls.
|
|
||||||
type DNSError struct {
|
return ""
|
||||||
Message string
|
|
||||||
NS string
|
|
||||||
MsgIn *dns.Msg
|
|
||||||
MsgOut *dns.Msg
|
|
||||||
Err error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSError) Error() string {
|
|
||||||
var details []string
|
|
||||||
if d.NS != "" {
|
|
||||||
details = append(details, "ns="+d.NS)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.MsgIn != nil && len(d.MsgIn.Question) > 0 {
|
|
||||||
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgIn.Question)))
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.MsgOut != nil {
|
|
||||||
if d.MsgIn == nil || len(d.MsgIn.Question) == 0 {
|
|
||||||
details = append(details, fmt.Sprintf("question='%s'", formatQuestions(d.MsgOut.Question)))
|
|
||||||
}
|
|
||||||
|
|
||||||
details = append(details, "code="+dns.RcodeToString[d.MsgOut.Rcode])
|
|
||||||
}
|
|
||||||
|
|
||||||
msg := "DNS error"
|
|
||||||
if d.Message != "" {
|
|
||||||
msg = d.Message
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.Err != nil {
|
|
||||||
msg += ": " + d.Err.Error()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(details) > 0 {
|
|
||||||
msg += " [" + strings.Join(details, ", ") + "]"
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DNSError) Unwrap() error {
|
|
||||||
return d.Err
|
|
||||||
}
|
|
||||||
|
|
||||||
func formatQuestions(questions []dns.Question) string {
|
|
||||||
var parts []string
|
|
||||||
for _, question := range questions {
|
|
||||||
parts = append(parts, strings.ReplaceAll(strings.TrimPrefix(question.String(), ";"), "\t", " "))
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.Join(parts, ";")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_unix.go
generated
vendored
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_unix.go
generated
vendored
|
|
@ -1,8 +0,0 @@
|
||||||
//go:build !windows
|
|
||||||
|
|
||||||
package dns01
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// dnsTimeout is used to override the default DNS timeout of 10 seconds.
|
|
||||||
var dnsTimeout = 10 * time.Second
|
|
||||||
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_windows.go
generated
vendored
8
vendor/github.com/go-acme/lego/v4/challenge/dns01/nameserver_windows.go
generated
vendored
|
|
@ -1,8 +0,0 @@
|
||||||
//go:build windows
|
|
||||||
|
|
||||||
package dns01
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// dnsTimeout is used to override the default DNS timeout of 20 seconds.
|
|
||||||
var dnsTimeout = 20 * time.Second
|
|
||||||
79
vendor/github.com/go-acme/lego/v4/challenge/dns01/precheck.go
generated
vendored
79
vendor/github.com/go-acme/lego/v4/challenge/dns01/precheck.go
generated
vendored
|
|
@ -4,15 +4,10 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
)
|
)
|
||||||
|
|
||||||
// defaultNameserverPort used by authoritative NS.
|
|
||||||
// This is for tests only.
|
|
||||||
var defaultNameserverPort = "53"
|
|
||||||
|
|
||||||
// PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
// PreCheckFunc checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||||
type PreCheckFunc func(fqdn, value string) (bool, error)
|
type PreCheckFunc func(fqdn, value string) (bool, error)
|
||||||
|
|
||||||
|
|
@ -28,53 +23,23 @@ func WrapPreCheck(wrap WrapPreCheckFunc) ChallengeOption {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisableCompletePropagationRequirement obsolete.
|
|
||||||
//
|
|
||||||
// Deprecated: use DisableAuthoritativeNssPropagationRequirement instead.
|
|
||||||
func DisableCompletePropagationRequirement() ChallengeOption {
|
func DisableCompletePropagationRequirement() ChallengeOption {
|
||||||
return DisableAuthoritativeNssPropagationRequirement()
|
|
||||||
}
|
|
||||||
|
|
||||||
func DisableAuthoritativeNssPropagationRequirement() ChallengeOption {
|
|
||||||
return func(chlg *Challenge) error {
|
return func(chlg *Challenge) error {
|
||||||
chlg.preCheck.requireAuthoritativeNssPropagation = false
|
chlg.preCheck.requireCompletePropagation = false
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func RecursiveNSsPropagationRequirement() ChallengeOption {
|
|
||||||
return func(chlg *Challenge) error {
|
|
||||||
chlg.preCheck.requireRecursiveNssPropagation = true
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func PropagationWait(wait time.Duration, skipCheck bool) ChallengeOption {
|
|
||||||
return WrapPreCheck(func(domain, fqdn, value string, check PreCheckFunc) (bool, error) {
|
|
||||||
time.Sleep(wait)
|
|
||||||
|
|
||||||
if skipCheck {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return check(fqdn, value)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
type preCheck struct {
|
type preCheck struct {
|
||||||
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
// checks DNS propagation before notifying ACME that the DNS challenge is ready.
|
||||||
checkFunc WrapPreCheckFunc
|
checkFunc WrapPreCheckFunc
|
||||||
|
|
||||||
// require the TXT record to be propagated to all authoritative name servers
|
// require the TXT record to be propagated to all authoritative name servers
|
||||||
requireAuthoritativeNssPropagation bool
|
requireCompletePropagation bool
|
||||||
|
|
||||||
// require the TXT record to be propagated to all recursive name servers
|
|
||||||
requireRecursiveNssPropagation bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newPreCheck() preCheck {
|
func newPreCheck() preCheck {
|
||||||
return preCheck{
|
return preCheck{
|
||||||
requireAuthoritativeNssPropagation: true,
|
requireCompletePropagation: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -88,48 +53,32 @@ func (p preCheck) call(domain, fqdn, value string) (bool, error) {
|
||||||
|
|
||||||
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
// checkDNSPropagation checks if the expected TXT record has been propagated to all authoritative nameservers.
|
||||||
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
|
func (p preCheck) checkDNSPropagation(fqdn, value string) (bool, error) {
|
||||||
// Initial attempt to resolve at the recursive NS (require to get CNAME)
|
// Initial attempt to resolve at the recursive NS
|
||||||
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
|
r, err := dnsQuery(fqdn, dns.TypeTXT, recursiveNameservers, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("initial recursive nameserver: %w", err)
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.requireCompletePropagation {
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Rcode == dns.RcodeSuccess {
|
if r.Rcode == dns.RcodeSuccess {
|
||||||
fqdn = updateDomainWithCName(r, fqdn)
|
fqdn = updateDomainWithCName(r, fqdn)
|
||||||
}
|
}
|
||||||
|
|
||||||
if p.requireRecursiveNssPropagation {
|
|
||||||
_, err = checkNameserversPropagation(fqdn, value, recursiveNameservers, false)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("recursive nameservers: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !p.requireAuthoritativeNssPropagation {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
authoritativeNss, err := lookupNameservers(fqdn)
|
authoritativeNss, err := lookupNameservers(fqdn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
found, err := checkNameserversPropagation(fqdn, value, authoritativeNss, true)
|
return checkAuthoritativeNss(fqdn, value, authoritativeNss)
|
||||||
if err != nil {
|
|
||||||
return found, fmt.Errorf("authoritative nameservers: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return found, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkNameserversPropagation queries each of the given nameservers for the expected TXT record.
|
// checkAuthoritativeNss queries each of the given nameservers for the expected TXT record.
|
||||||
func checkNameserversPropagation(fqdn, value string, nameservers []string, addPort bool) (bool, error) {
|
func checkAuthoritativeNss(fqdn, value string, nameservers []string) (bool, error) {
|
||||||
for _, ns := range nameservers {
|
for _, ns := range nameservers {
|
||||||
if addPort {
|
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{net.JoinHostPort(ns, "53")}, false)
|
||||||
ns = net.JoinHostPort(ns, defaultNameserverPort)
|
|
||||||
}
|
|
||||||
|
|
||||||
r, err := dnsQuery(fqdn, dns.TypeTXT, []string{ns}, false)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
@ -141,11 +90,9 @@ func checkNameserversPropagation(fqdn, value string, nameservers []string, addPo
|
||||||
var records []string
|
var records []string
|
||||||
|
|
||||||
var found bool
|
var found bool
|
||||||
|
|
||||||
for _, rr := range r.Answer {
|
for _, rr := range r.Answer {
|
||||||
if txt, ok := rr.(*dns.TXT); ok {
|
if txt, ok := rr.(*dns.TXT); ok {
|
||||||
record := strings.Join(txt.Txt, "")
|
record := strings.Join(txt.Txt, "")
|
||||||
|
|
||||||
records = append(records, record)
|
records = append(records, record)
|
||||||
if record == value {
|
if record == value {
|
||||||
found = true
|
found = true
|
||||||
|
|
|
||||||
36
vendor/github.com/go-acme/lego/v4/challenge/http01/domain_matcher.go
generated
vendored
36
vendor/github.com/go-acme/lego/v4/challenge/http01/domain_matcher.go
generated
vendored
|
|
@ -3,7 +3,6 @@ package http01
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/netip"
|
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,16 +23,13 @@ import (
|
||||||
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
|
// RFC7239 has standardized the different forwarding headers into a single header named Forwarded.
|
||||||
// The header value has a different format, so you should use forwardedMatcher
|
// The header value has a different format, so you should use forwardedMatcher
|
||||||
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
|
// when the http01.ProviderServer operates behind a RFC7239 compatible proxy.
|
||||||
// https://www.rfc-editor.org/rfc/rfc7239.html
|
// https://tools.ietf.org/html/rfc7239
|
||||||
//
|
//
|
||||||
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
|
// Note: RFC7239 also reminds us, "that an HTTP list [...] may be split over multiple header fields" (section 7.1),
|
||||||
// meaning that
|
// meaning that
|
||||||
//
|
|
||||||
// X-Header: a
|
// X-Header: a
|
||||||
// X-Header: b
|
// X-Header: b
|
||||||
//
|
|
||||||
// is equal to
|
// is equal to
|
||||||
//
|
|
||||||
// X-Header: a, b
|
// X-Header: a, b
|
||||||
//
|
//
|
||||||
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
|
// All matcher implementations (explicitly not excluding arbitraryMatcher!)
|
||||||
|
|
@ -55,10 +51,10 @@ func (m *hostMatcher) name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *hostMatcher) matches(r *http.Request, domain string) bool {
|
func (m *hostMatcher) matches(r *http.Request, domain string) bool {
|
||||||
return matchDomain(r.Host, domain)
|
return strings.HasPrefix(r.Host, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// arbitraryMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
|
// hostMatcher checks whether the specified (*net/http.Request).Header value starts with a domain name.
|
||||||
type arbitraryMatcher string
|
type arbitraryMatcher string
|
||||||
|
|
||||||
func (m arbitraryMatcher) name() string {
|
func (m arbitraryMatcher) name() string {
|
||||||
|
|
@ -66,11 +62,11 @@ func (m arbitraryMatcher) name() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
|
func (m arbitraryMatcher) matches(r *http.Request, domain string) bool {
|
||||||
return matchDomain(r.Header.Get(m.name()), domain)
|
return strings.HasPrefix(r.Header.Get(m.name()), domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
|
// forwardedMatcher checks whether the Forwarded header contains a "host" element starting with a domain name.
|
||||||
// See https://www.rfc-editor.org/rfc/rfc7239.html for details.
|
// See https://tools.ietf.org/html/rfc7239 for details.
|
||||||
type forwardedMatcher struct{}
|
type forwardedMatcher struct{}
|
||||||
|
|
||||||
func (m *forwardedMatcher) name() string {
|
func (m *forwardedMatcher) name() string {
|
||||||
|
|
@ -88,8 +84,7 @@ func (m *forwardedMatcher) matches(r *http.Request, domain string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
host := fwds[0]["host"]
|
host := fwds[0]["host"]
|
||||||
|
return strings.HasPrefix(host, domain)
|
||||||
return matchDomain(host, domain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// parsing requires some form of state machine.
|
// parsing requires some form of state machine.
|
||||||
|
|
@ -100,7 +95,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||||
inquote := false
|
inquote := false
|
||||||
|
|
||||||
pos := 0
|
pos := 0
|
||||||
|
|
||||||
l := len(s)
|
l := len(s)
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
r := rune(s[i])
|
r := rune(s[i])
|
||||||
|
|
@ -112,7 +106,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||||
pos = i
|
pos = i
|
||||||
inquote = false
|
inquote = false
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -121,7 +114,6 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||||
if key == "" {
|
if key == "" {
|
||||||
return nil, fmt.Errorf("unexpected quoted string as pos %d", i)
|
return nil, fmt.Errorf("unexpected quoted string as pos %d", i)
|
||||||
}
|
}
|
||||||
|
|
||||||
inquote = true
|
inquote = true
|
||||||
pos = i + 1
|
pos = i + 1
|
||||||
|
|
||||||
|
|
@ -138,10 +130,11 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||||
|
|
||||||
case r == ',': // end of forwarded-element
|
case r == ',': // end of forwarded-element
|
||||||
if key != "" {
|
if key != "" {
|
||||||
|
if val == "" {
|
||||||
val = s[pos:i]
|
val = s[pos:i]
|
||||||
|
}
|
||||||
cur[key] = val
|
cur[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
elements = append(elements, cur)
|
elements = append(elements, cur)
|
||||||
cur = make(map[string]string)
|
cur = make(map[string]string)
|
||||||
key = ""
|
key = ""
|
||||||
|
|
@ -164,14 +157,11 @@ func parseForwardedHeader(s string) (elements []map[string]string, err error) {
|
||||||
if pos < len(s) {
|
if pos < len(s) {
|
||||||
val = s[pos:]
|
val = s[pos:]
|
||||||
}
|
}
|
||||||
|
|
||||||
cur[key] = val
|
cur[key] = val
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(cur) > 0 {
|
if len(cur) > 0 {
|
||||||
elements = append(elements, cur)
|
elements = append(elements, cur)
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements, nil
|
return elements, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -186,19 +176,9 @@ func skipWS(s string, i int) int {
|
||||||
for isWS(rune(s[i+1])) {
|
for isWS(rune(s[i+1])) {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
|
|
||||||
return i
|
return i
|
||||||
}
|
}
|
||||||
|
|
||||||
func isWS(r rune) bool {
|
func isWS(r rune) bool {
|
||||||
return strings.ContainsRune(" \t\v\r\n", r)
|
return strings.ContainsRune(" \t\v\r\n", r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func matchDomain(src, domain string) bool {
|
|
||||||
addr, err := netip.ParseAddr(domain)
|
|
||||||
if err == nil && addr.Is6() {
|
|
||||||
domain = "[" + domain + "]"
|
|
||||||
}
|
|
||||||
|
|
||||||
return strings.HasPrefix(src, domain)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
31
vendor/github.com/go-acme/lego/v4/challenge/http01/http_challenge.go
generated
vendored
31
vendor/github.com/go-acme/lego/v4/challenge/http01/http_challenge.go
generated
vendored
|
|
@ -2,7 +2,6 @@ package http01
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/acme/api"
|
"github.com/go-acme/lego/v4/acme/api"
|
||||||
|
|
@ -12,16 +11,6 @@ import (
|
||||||
|
|
||||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||||
|
|
||||||
type ChallengeOption func(*Challenge) error
|
|
||||||
|
|
||||||
// SetDelay sets a delay between the start of the HTTP server and the challenge validation.
|
|
||||||
func SetDelay(delay time.Duration) ChallengeOption {
|
|
||||||
return func(chlg *Challenge) error {
|
|
||||||
chlg.delay = delay
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChallengePath returns the URL path for the `http-01` challenge.
|
// ChallengePath returns the URL path for the `http-01` challenge.
|
||||||
func ChallengePath(token string) string {
|
func ChallengePath(token string) string {
|
||||||
return "/.well-known/acme-challenge/" + token
|
return "/.well-known/acme-challenge/" + token
|
||||||
|
|
@ -31,24 +20,14 @@ type Challenge struct {
|
||||||
core *api.Core
|
core *api.Core
|
||||||
validate ValidateFunc
|
validate ValidateFunc
|
||||||
provider challenge.Provider
|
provider challenge.Provider
|
||||||
delay time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
|
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||||
chlg := &Challenge{
|
return &Challenge{
|
||||||
core: core,
|
core: core,
|
||||||
validate: validate,
|
validate: validate,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
err := opt(chlg)
|
|
||||||
if err != nil {
|
|
||||||
log.Infof("challenge option error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chlg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||||
|
|
@ -74,7 +53,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
|
return fmt.Errorf("[%s] acme: error presenting token: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
err := c.provider.CleanUp(authz.Identifier.Value, chlng.Token, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -82,11 +60,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if c.delay > 0 {
|
|
||||||
time.Sleep(c.delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
chlng.KeyAuthorization = keyAuth
|
chlng.KeyAuthorization = keyAuth
|
||||||
|
|
||||||
return c.validate(c.core, domain, chlng)
|
return c.validate(c.core, domain, chlng)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
vendor/github.com/go-acme/lego/v4/challenge/http01/http_challenge_server.go
generated
vendored
47
vendor/github.com/go-acme/lego/v4/challenge/http01/http_challenge_server.go
generated
vendored
|
|
@ -2,11 +2,9 @@ package http01
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/fs"
|
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/textproto"
|
"net/textproto"
|
||||||
"os"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
|
|
@ -16,11 +14,8 @@ import (
|
||||||
// It may be instantiated without using the NewProviderServer function if
|
// It may be instantiated without using the NewProviderServer function if
|
||||||
// you want only to use the default values.
|
// you want only to use the default values.
|
||||||
type ProviderServer struct {
|
type ProviderServer struct {
|
||||||
address string
|
iface string
|
||||||
network string // must be valid argument to net.Listen
|
port string
|
||||||
|
|
||||||
socketMode fs.FileMode
|
|
||||||
|
|
||||||
matcher domainMatcher
|
matcher domainMatcher
|
||||||
done chan bool
|
done chan bool
|
||||||
listener net.Listener
|
listener net.Listener
|
||||||
|
|
@ -34,37 +29,24 @@ func NewProviderServer(iface, port string) *ProviderServer {
|
||||||
port = "80"
|
port = "80"
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ProviderServer{network: "tcp", address: net.JoinHostPort(iface, port), matcher: &hostMatcher{}}
|
return &ProviderServer{iface: iface, port: port, matcher: &hostMatcher{}}
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnixProviderServer(socketPath string, mode fs.FileMode) *ProviderServer {
|
|
||||||
return &ProviderServer{network: "unix", address: socketPath, socketMode: mode, matcher: &hostMatcher{}}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
// Present starts a web server and makes the token available at `ChallengePath(token)` for web requests.
|
||||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
var err error
|
var err error
|
||||||
|
s.listener, err = net.Listen("tcp", s.GetAddress())
|
||||||
s.listener, err = net.Listen(s.network, s.GetAddress())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
|
return fmt.Errorf("could not start HTTP server for challenge: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.network == "unix" {
|
|
||||||
if err = os.Chmod(s.address, s.socketMode); err != nil {
|
|
||||||
return fmt.Errorf("chmod %s: %w", s.address, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
s.done = make(chan bool)
|
s.done = make(chan bool)
|
||||||
|
|
||||||
go s.serve(domain, token, keyAuth)
|
go s.serve(domain, token, keyAuth)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ProviderServer) GetAddress() string {
|
func (s *ProviderServer) GetAddress() string {
|
||||||
return s.address
|
return net.JoinHostPort(s.iface, s.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`.
|
// CleanUp closes the HTTP server and removes the token from `ChallengePath(token)`.
|
||||||
|
|
@ -72,11 +54,8 @@ func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||||
if s.listener == nil {
|
if s.listener == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
s.listener.Close()
|
s.listener.Close()
|
||||||
|
|
||||||
<-s.done
|
<-s.done
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,7 +69,7 @@ func (s *ProviderServer) CleanUp(domain, token, keyAuth string) error {
|
||||||
//
|
//
|
||||||
// The exact behavior depends on the value of headerName:
|
// The exact behavior depends on the value of headerName:
|
||||||
// - "" (the empty string) and "Host" will restore the default and only check the Host header
|
// - "" (the empty string) and "Host" will restore the default and only check the Host header
|
||||||
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://www.rfc-editor.org/rfc/rfc7239.html
|
// - "Forwarded" will look for a Forwarded header, and inspect it according to https://tools.ietf.org/html/rfc7239
|
||||||
// - any other value will check the header value with the same name.
|
// - any other value will check the header value with the same name.
|
||||||
func (s *ProviderServer) SetProxyHeader(headerName string) {
|
func (s *ProviderServer) SetProxyHeader(headerName string) {
|
||||||
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
|
switch h := textproto.CanonicalMIMEHeaderKey(headerName); h {
|
||||||
|
|
@ -106,32 +85,27 @@ func (s *ProviderServer) SetProxyHeader(headerName string) {
|
||||||
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||||
path := ChallengePath(token)
|
path := ChallengePath(token)
|
||||||
|
|
||||||
// The incoming request will be validated to prevent DNS rebind attacks.
|
// The incoming request must will be validated to prevent DNS rebind attacks.
|
||||||
// We only respond with the keyAuth, when we're receiving a GET requests with
|
// We only respond with the keyAuth, when we're receiving a GET requests with
|
||||||
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
|
// the "Host" header matching the domain (the latter is configurable though SetProxyHeader).
|
||||||
mux := http.NewServeMux()
|
mux := http.NewServeMux()
|
||||||
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc(path, func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method == http.MethodGet && s.matcher.matches(r, domain) {
|
if r.Method == http.MethodGet && s.matcher.matches(r, domain) {
|
||||||
w.Header().Set("Content-Type", "text/plain")
|
w.Header().Set("Content-Type", "text/plain")
|
||||||
|
|
||||||
_, err := w.Write([]byte(keyAuth))
|
_, err := w.Write([]byte(keyAuth))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] Served key authentication", domain)
|
log.Infof("[%s] Served key authentication", domain)
|
||||||
|
} else {
|
||||||
return
|
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure your are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
|
||||||
}
|
|
||||||
|
|
||||||
log.Warnf("Received request for domain %s with method %s but the domain did not match any challenge. Please ensure you are passing the %s header properly.", r.Host, r.Method, s.matcher.name())
|
|
||||||
|
|
||||||
_, err := w.Write([]byte("TEST"))
|
_, err := w.Write([]byte("TEST"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
httpServer := &http.Server{Handler: mux}
|
httpServer := &http.Server{Handler: mux}
|
||||||
|
|
@ -144,6 +118,5 @@ func (s *ProviderServer) serve(domain, token, keyAuth string) {
|
||||||
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
if err != nil && !strings.Contains(err.Error(), "use of closed network connection") {
|
||||||
log.Println(err)
|
log.Println(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.done <- true
|
s.done <- true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
4
vendor/github.com/go-acme/lego/v4/challenge/resolver/errors.go
generated
vendored
4
vendor/github.com/go-acme/lego/v4/challenge/resolver/errors.go
generated
vendored
|
|
@ -16,12 +16,10 @@ func (e obtainError) Error() string {
|
||||||
for domain := range e {
|
for domain := range e {
|
||||||
domains = append(domains, domain)
|
domains = append(domains, domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Strings(domains)
|
sort.Strings(domains)
|
||||||
|
|
||||||
for _, domain := range domains {
|
for _, domain := range domains {
|
||||||
_, _ = fmt.Fprintf(buffer, "[%s] %s\n", domain, e[domain])
|
buffer.WriteString(fmt.Sprintf("[%s] %s\n", domain, e[domain]))
|
||||||
}
|
}
|
||||||
|
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
16
vendor/github.com/go-acme/lego/v4/challenge/resolver/prober.go
generated
vendored
16
vendor/github.com/go-acme/lego/v4/challenge/resolver/prober.go
generated
vendored
|
|
@ -50,14 +50,11 @@ func NewProber(solverManager *SolverManager) *Prober {
|
||||||
func (p *Prober) Solve(authorizations []acme.Authorization) error {
|
func (p *Prober) Solve(authorizations []acme.Authorization) error {
|
||||||
failures := make(obtainError)
|
failures := make(obtainError)
|
||||||
|
|
||||||
var (
|
var authSolvers []*selectedAuthSolver
|
||||||
authSolvers []*selectedAuthSolver
|
var authSolversSequential []*selectedAuthSolver
|
||||||
authSolversSequential []*selectedAuthSolver
|
|
||||||
)
|
|
||||||
|
|
||||||
// Loop through the resources, basically through the domains.
|
// Loop through the resources, basically through the domains.
|
||||||
// First pass just selects a solver for each authz.
|
// First pass just selects a solver for each authz.
|
||||||
|
|
||||||
for _, authz := range authorizations {
|
for _, authz := range authorizations {
|
||||||
domain := challenge.GetTargetedDomain(authz)
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
if authz.Status == acme.StatusValid {
|
if authz.Status == acme.StatusValid {
|
||||||
|
|
@ -93,7 +90,6 @@ func (p *Prober) Solve(authorizations []acme.Authorization) error {
|
||||||
if len(failures) > 0 {
|
if len(failures) > 0 {
|
||||||
return failures
|
return failures
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -106,9 +102,7 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
err := solvr.PreSolve(authSolver.authz)
|
err := solvr.PreSolve(authSolver.authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failures[domain] = err
|
failures[domain] = err
|
||||||
|
|
||||||
cleanUp(authSolver.solver, authSolver.authz)
|
cleanUp(authSolver.solver, authSolver.authz)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,9 +111,7 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
err := authSolver.solver.Solve(authSolver.authz)
|
err := authSolver.solver.Solve(authSolver.authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
failures[domain] = err
|
failures[domain] = err
|
||||||
|
|
||||||
cleanUp(authSolver.solver, authSolver.authz)
|
cleanUp(authSolver.solver, authSolver.authz)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +128,7 @@ func sequentialSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
// For all valid preSolvers, first submit the challenges, so they have max time to propagate
|
// For all valid preSolvers, first submit the challenges so they have max time to propagate
|
||||||
for _, authSolver := range authSolvers {
|
for _, authSolver := range authSolvers {
|
||||||
authz := authSolver.authz
|
authz := authSolver.authz
|
||||||
if solvr, ok := authSolver.solver.(preSolver); ok {
|
if solvr, ok := authSolver.solver.(preSolver); ok {
|
||||||
|
|
@ -157,7 +149,6 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
// Finally solve all challenges for real
|
// Finally solve all challenges for real
|
||||||
for _, authSolver := range authSolvers {
|
for _, authSolver := range authSolvers {
|
||||||
authz := authSolver.authz
|
authz := authSolver.authz
|
||||||
|
|
||||||
domain := challenge.GetTargetedDomain(authz)
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
if failures[domain] != nil {
|
if failures[domain] != nil {
|
||||||
// already failed in previous loop
|
// already failed in previous loop
|
||||||
|
|
@ -174,7 +165,6 @@ func parallelSolve(authSolvers []*selectedAuthSolver, failures obtainError) {
|
||||||
func cleanUp(solvr solver, authz acme.Authorization) {
|
func cleanUp(solvr solver, authz acme.Authorization) {
|
||||||
if solvr, ok := solvr.(cleanup); ok {
|
if solvr, ok := solvr.(cleanup); ok {
|
||||||
domain := challenge.GetTargetedDomain(authz)
|
domain := challenge.GetTargetedDomain(authz)
|
||||||
|
|
||||||
err := solvr.CleanUp(authz)
|
err := solvr.CleanUp(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err)
|
log.Warnf("[%s] acme: cleaning up failed: %v ", domain, err)
|
||||||
|
|
|
||||||
43
vendor/github.com/go-acme/lego/v4/challenge/resolver/solver_manager.go
generated
vendored
43
vendor/github.com/go-acme/lego/v4/challenge/resolver/solver_manager.go
generated
vendored
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v5"
|
"github.com/cenkalti/backoff/v4"
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/acme/api"
|
"github.com/go-acme/lego/v4/acme/api"
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"github.com/go-acme/lego/v4/challenge/http01"
|
"github.com/go-acme/lego/v4/challenge/http01"
|
||||||
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
"github.com/go-acme/lego/v4/challenge/tlsalpn01"
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
"github.com/go-acme/lego/v4/platform/wait"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type byType []acme.Challenge
|
type byType []acme.Challenge
|
||||||
|
|
@ -38,14 +37,14 @@ func NewSolversManager(core *api.Core) *SolverManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
|
// SetHTTP01Provider specifies a custom provider p that can solve the given HTTP-01 challenge.
|
||||||
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider, opts ...http01.ChallengeOption) error {
|
func (c *SolverManager) SetHTTP01Provider(p challenge.Provider) error {
|
||||||
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p, opts...)
|
c.solvers[challenge.HTTP01] = http01.NewChallenge(c.core, validate, p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
|
// SetTLSALPN01Provider specifies a custom provider p that can solve the given TLS-ALPN-01 challenge.
|
||||||
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider, opts ...tlsalpn01.ChallengeOption) error {
|
func (c *SolverManager) SetTLSALPN01Provider(p challenge.Provider) error {
|
||||||
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p, opts...)
|
c.solvers[challenge.TLSALPN01] = tlsalpn01.NewChallenge(c.core, validate, p)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,7 +54,7 @@ func (c *SolverManager) SetDNS01Provider(p challenge.Provider, opts ...dns01.Cha
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove removes a challenge type from the available solvers.
|
// Remove Remove a challenge type from the available solvers.
|
||||||
func (c *SolverManager) Remove(chlgType challenge.Type) {
|
func (c *SolverManager) Remove(chlgType challenge.Type) {
|
||||||
delete(c.solvers, chlgType)
|
delete(c.solvers, chlgType)
|
||||||
}
|
}
|
||||||
|
|
@ -71,7 +70,6 @@ func (c *SolverManager) chooseSolver(authz acme.Authorization) solver {
|
||||||
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
|
log.Infof("[%s] acme: use %s solver", domain, chlg.Type)
|
||||||
return solvr
|
return solvr
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
|
log.Infof("[%s] acme: Could not find solver for: %s", domain, chlg.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -102,26 +100,28 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
||||||
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
|
// https://github.com/letsencrypt/boulder/blob/master/docs/acme-divergences.md#section-82
|
||||||
ra = 5
|
ra = 5
|
||||||
}
|
}
|
||||||
|
|
||||||
initialInterval := time.Duration(ra) * time.Second
|
initialInterval := time.Duration(ra) * time.Second
|
||||||
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
bo := backoff.NewExponentialBackOff()
|
bo := backoff.NewExponentialBackOff()
|
||||||
bo.InitialInterval = initialInterval
|
bo.InitialInterval = initialInterval
|
||||||
bo.MaxInterval = 10 * initialInterval
|
bo.MaxInterval = 10 * initialInterval
|
||||||
|
bo.MaxElapsedTime = 100 * initialInterval
|
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
|
||||||
// After the path is sent, the ACME server will access our server.
|
// After the path is sent, the ACME server will access our server.
|
||||||
// Repeatedly check the server for an updated status on our request.
|
// Repeatedly check the server for an updated status on our request.
|
||||||
operation := func() error {
|
operation := func() error {
|
||||||
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
authz, err := core.Authorizations.Get(chlng.AuthorizationURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return backoff.Permanent(err)
|
cancel()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
valid, err := checkAuthorizationStatus(authz)
|
valid, err := checkAuthorizationStatus(authz)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return backoff.Permanent(err)
|
cancel()
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if valid {
|
if valid {
|
||||||
|
|
@ -129,12 +129,10 @@ func validate(core *api.Core, domain string, chlg acme.Challenge) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Errorf("the server didn't respond to our request (status=%s)", authz.Status)
|
return errors.New("the server didn't respond to our request")
|
||||||
}
|
}
|
||||||
|
|
||||||
return wait.Retry(ctx, operation,
|
return backoff.Retry(operation, backoff.WithContext(bo, ctx))
|
||||||
backoff.WithBackOff(bo),
|
|
||||||
backoff.WithMaxElapsedTime(100*initialInterval))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
||||||
|
|
@ -144,9 +142,9 @@ func checkChallengeStatus(chlng acme.ExtendedChallenge) (bool, error) {
|
||||||
case acme.StatusPending, acme.StatusProcessing:
|
case acme.StatusPending, acme.StatusProcessing:
|
||||||
return false, nil
|
return false, nil
|
||||||
case acme.StatusInvalid:
|
case acme.StatusInvalid:
|
||||||
return false, fmt.Errorf("invalid challenge: %w", chlng.Err())
|
return false, chlng.Error
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("the server returned an unexpected challenge status: %s", chlng.Status)
|
return false, errors.New("the server returned an unexpected state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,12 +159,11 @@ func checkAuthorizationStatus(authz acme.Authorization) (bool, error) {
|
||||||
case acme.StatusInvalid:
|
case acme.StatusInvalid:
|
||||||
for _, chlg := range authz.Challenges {
|
for _, chlg := range authz.Challenges {
|
||||||
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
|
if chlg.Status == acme.StatusInvalid && chlg.Error != nil {
|
||||||
return false, fmt.Errorf("invalid authorization: %w", chlg.Err())
|
return false, chlg.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false, fmt.Errorf("the authorization state %s", authz.Status)
|
||||||
return false, errors.New("invalid authorization")
|
|
||||||
default:
|
default:
|
||||||
return false, fmt.Errorf("the server returned an unexpected authorization status: %s", authz.Status)
|
return false, errors.New("the server returned an unexpected state")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
35
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
35
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge.go
generated
vendored
|
|
@ -7,7 +7,6 @@ import (
|
||||||
"crypto/x509/pkix"
|
"crypto/x509/pkix"
|
||||||
"encoding/asn1"
|
"encoding/asn1"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/acme"
|
"github.com/go-acme/lego/v4/acme"
|
||||||
"github.com/go-acme/lego/v4/acme/api"
|
"github.com/go-acme/lego/v4/acme/api"
|
||||||
|
|
@ -17,43 +16,23 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
// idPeAcmeIdentifierV1 is the SMI Security for PKIX Certification Extension OID referencing the ACME extension.
|
||||||
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-6.1
|
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.1
|
||||||
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
var idPeAcmeIdentifierV1 = asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 31}
|
||||||
|
|
||||||
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
type ValidateFunc func(core *api.Core, domain string, chlng acme.Challenge) error
|
||||||
|
|
||||||
type ChallengeOption func(*Challenge) error
|
|
||||||
|
|
||||||
// SetDelay sets a delay between the start of the TLS listener and the challenge validation.
|
|
||||||
func SetDelay(delay time.Duration) ChallengeOption {
|
|
||||||
return func(chlg *Challenge) error {
|
|
||||||
chlg.delay = delay
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Challenge struct {
|
type Challenge struct {
|
||||||
core *api.Core
|
core *api.Core
|
||||||
validate ValidateFunc
|
validate ValidateFunc
|
||||||
provider challenge.Provider
|
provider challenge.Provider
|
||||||
delay time.Duration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider, opts ...ChallengeOption) *Challenge {
|
func NewChallenge(core *api.Core, validate ValidateFunc, provider challenge.Provider) *Challenge {
|
||||||
chlg := &Challenge{
|
return &Challenge{
|
||||||
core: core,
|
core: core,
|
||||||
validate: validate,
|
validate: validate,
|
||||||
provider: provider,
|
provider: provider,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
err := opt(chlg)
|
|
||||||
if err != nil {
|
|
||||||
log.Infof("challenge option error: %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chlg
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
func (c *Challenge) SetProvider(provider challenge.Provider) {
|
||||||
|
|
@ -80,7 +59,6 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
|
return fmt.Errorf("[%s] acme: error presenting token: %w", challenge.GetTargetedDomain(authz), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
|
err := c.provider.CleanUp(domain, chlng.Token, keyAuth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -88,12 +66,7 @@ func (c *Challenge) Solve(authz acme.Authorization) error {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if c.delay > 0 {
|
|
||||||
time.Sleep(c.delay)
|
|
||||||
}
|
|
||||||
|
|
||||||
chlng.KeyAuthorization = keyAuth
|
chlng.KeyAuthorization = keyAuth
|
||||||
|
|
||||||
return c.validate(c.core, domain, chlng)
|
return c.validate(c.core, domain, chlng)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,7 +83,7 @@ func ChallengeBlocks(domain, keyAuth string) ([]byte, []byte, error) {
|
||||||
|
|
||||||
// Add the keyAuth digest as the acmeValidation-v1 extension
|
// Add the keyAuth digest as the acmeValidation-v1 extension
|
||||||
// (marked as critical such that it won't be used by non-ACME software).
|
// (marked as critical such that it won't be used by non-ACME software).
|
||||||
// Reference: https://www.rfc-editor.org/rfc/rfc8737.html#section-3
|
// Reference: https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-3
|
||||||
extensions := []pkix.Extension{
|
extensions := []pkix.Extension{
|
||||||
{
|
{
|
||||||
Id: idPeAcmeIdentifierV1,
|
Id: idPeAcmeIdentifierV1,
|
||||||
|
|
|
||||||
4
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
4
vendor/github.com/go-acme/lego/v4/challenge/tlsalpn01/tls_alpn_challenge_server.go
generated
vendored
|
|
@ -40,7 +40,7 @@ func (s *ProviderServer) GetAddress() string {
|
||||||
return net.JoinHostPort(s.iface, s.port)
|
return net.JoinHostPort(s.iface, s.port)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Present generates a certificate with an SHA-256 digest of the keyAuth provided
|
// Present generates a certificate with a SHA-256 digest of the keyAuth provided
|
||||||
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
// as the acmeValidation-v1 extension value to conform to the ACME-TLS-ALPN spec.
|
||||||
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
if s.port == "" {
|
if s.port == "" {
|
||||||
|
|
@ -61,7 +61,7 @@ func (s *ProviderServer) Present(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
// We must set that the `acme-tls/1` application level protocol is supported
|
// We must set that the `acme-tls/1` application level protocol is supported
|
||||||
// so that the protocol negotiation can succeed. Reference:
|
// so that the protocol negotiation can succeed. Reference:
|
||||||
// https://www.rfc-editor.org/rfc/rfc8737.html#section-6.2
|
// https://tools.ietf.org/html/draft-ietf-acme-tls-alpn-07#section-6.2
|
||||||
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
tlsConf.NextProtos = []string{ACMETLS1Protocol}
|
||||||
|
|
||||||
// Create the listener with the created tls.Config.
|
// Create the listener with the created tls.Config.
|
||||||
|
|
|
||||||
10
vendor/github.com/go-acme/lego/v4/lego/client.go
generated
vendored
10
vendor/github.com/go-acme/lego/v4/lego/client.go
generated
vendored
|
|
@ -53,15 +53,7 @@ func NewClient(config *Config) (*Client, error) {
|
||||||
solversManager := resolver.NewSolversManager(core)
|
solversManager := resolver.NewSolversManager(core)
|
||||||
|
|
||||||
prober := resolver.NewProber(solversManager)
|
prober := resolver.NewProber(solversManager)
|
||||||
|
certifier := certificate.NewCertifier(core, prober, certificate.CertifierOptions{KeyType: config.Certificate.KeyType, Timeout: config.Certificate.Timeout})
|
||||||
options := certificate.CertifierOptions{
|
|
||||||
KeyType: config.Certificate.KeyType,
|
|
||||||
Timeout: config.Certificate.Timeout,
|
|
||||||
OverallRequestLimit: config.Certificate.OverallRequestLimit,
|
|
||||||
DisableCommonName: config.Certificate.DisableCommonName,
|
|
||||||
}
|
|
||||||
|
|
||||||
certifier := certificate.NewCertifier(core, prober, options)
|
|
||||||
|
|
||||||
return &Client{
|
return &Client{
|
||||||
Certificate: certifier,
|
Certificate: certifier,
|
||||||
|
|
|
||||||
77
vendor/github.com/go-acme/lego/v4/lego/client_config.go
generated
vendored
77
vendor/github.com/go-acme/lego/v4/lego/client_config.go
generated
vendored
|
|
@ -4,11 +4,10 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
|
|
@ -18,18 +17,13 @@ import (
|
||||||
const (
|
const (
|
||||||
// caCertificatesEnvVar is the environment variable name that can be used to
|
// caCertificatesEnvVar is the environment variable name that can be used to
|
||||||
// specify the path to PEM encoded CA Certificates that can be used to
|
// specify the path to PEM encoded CA Certificates that can be used to
|
||||||
// authenticate an ACME server with an HTTPS certificate not issued by a CA in
|
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
||||||
// the system-wide trusted root list.
|
// the system-wide trusted root list.
|
||||||
// Multiple file paths can be added by using os.PathListSeparator as a separator.
|
|
||||||
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
caCertificatesEnvVar = "LEGO_CA_CERTIFICATES"
|
||||||
|
|
||||||
// caSystemCertPool is the environment variable name that can be used to define
|
|
||||||
// if the certificates pool must use a copy of the system cert pool.
|
|
||||||
caSystemCertPool = "LEGO_CA_SYSTEM_CERT_POOL"
|
|
||||||
|
|
||||||
// caServerNameEnvVar is the environment variable name that can be used to
|
// caServerNameEnvVar is the environment variable name that can be used to
|
||||||
// specify the CA server name that can be used to
|
// specify the CA server name that can be used to
|
||||||
// authenticate an ACME server with an HTTPS certificate not issued by a CA in
|
// authenticate an ACME server with a HTTPS certificate not issued by a CA in
|
||||||
// the system-wide trusted root list.
|
// the system-wide trusted root list.
|
||||||
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
caServerNameEnvVar = "LEGO_CA_SERVER_NAME"
|
||||||
|
|
||||||
|
|
@ -63,8 +57,6 @@ func NewConfig(user registration.User) *Config {
|
||||||
type CertificateConfig struct {
|
type CertificateConfig struct {
|
||||||
KeyType certcrypto.KeyType
|
KeyType certcrypto.KeyType
|
||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
OverallRequestLimit int
|
|
||||||
DisableCommonName bool
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
|
// createDefaultHTTPClient Creates an HTTP client with a reasonable timeout value
|
||||||
|
|
@ -90,60 +82,23 @@ func createDefaultHTTPClient() *http.Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
// initCertPool creates a *x509.CertPool populated with the PEM certificates
|
||||||
// found in the filepath specified in the caCertificatesEnvVar OS environment variable.
|
// found in the filepath specified in the caCertificatesEnvVar OS environment
|
||||||
// If the caCertificatesEnvVar is not set then initCertPool will return nil.
|
// variable. If the caCertificatesEnvVar is not set then initCertPool will
|
||||||
// If there is an error creating a *x509.CertPool from the provided caCertificatesEnvVar value then initCertPool will panic.
|
// return nil. If there is an error creating a *x509.CertPool from the provided
|
||||||
// If the caSystemCertPool is set to a "truthy value" (`1`, `t`, `T`, `TRUE`, `true`, `True`) then a copy of system cert pool will be used.
|
// caCertificatesEnvVar value then initCertPool will panic.
|
||||||
// caSystemCertPool requires caCertificatesEnvVar to be set.
|
|
||||||
func initCertPool() *x509.CertPool {
|
func initCertPool() *x509.CertPool {
|
||||||
customCACertsPath := os.Getenv(caCertificatesEnvVar)
|
if customCACertsPath := os.Getenv(caCertificatesEnvVar); customCACertsPath != "" {
|
||||||
if customCACertsPath == "" {
|
customCAs, err := ioutil.ReadFile(customCACertsPath)
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
useSystemCertPool, _ := strconv.ParseBool(os.Getenv(caSystemCertPool))
|
|
||||||
|
|
||||||
caCerts := strings.Split(customCACertsPath, string(os.PathListSeparator))
|
|
||||||
|
|
||||||
certPool, err := CreateCertPool(caCerts, useSystemCertPool)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("create certificates pool: %v", err))
|
panic(fmt.Sprintf("error reading %s=%q: %v",
|
||||||
|
caCertificatesEnvVar, customCACertsPath, err))
|
||||||
}
|
}
|
||||||
|
certPool := x509.NewCertPool()
|
||||||
return certPool
|
|
||||||
}
|
|
||||||
|
|
||||||
// CreateCertPool creates a *x509.CertPool populated with the PEM certificates.
|
|
||||||
func CreateCertPool(caCerts []string, useSystemCertPool bool) (*x509.CertPool, error) {
|
|
||||||
if len(caCerts) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
certPool := newCertPool(useSystemCertPool)
|
|
||||||
|
|
||||||
for _, customPath := range caCerts {
|
|
||||||
customCAs, err := os.ReadFile(customPath)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("error reading %q: %w", customPath, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
if ok := certPool.AppendCertsFromPEM(customCAs); !ok {
|
||||||
return nil, fmt.Errorf("error creating x509 cert pool from %q: %w", customPath, err)
|
panic(fmt.Sprintf("error creating x509 cert pool from %s=%q: %v",
|
||||||
|
caCertificatesEnvVar, customCACertsPath, err))
|
||||||
}
|
}
|
||||||
|
return certPool
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return certPool, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newCertPool(useSystemCertPool bool) *x509.CertPool {
|
|
||||||
if !useSystemCertPool {
|
|
||||||
return x509.NewCertPool()
|
|
||||||
}
|
|
||||||
|
|
||||||
pool, err := x509.SystemCertPool()
|
|
||||||
if err == nil {
|
|
||||||
return pool
|
|
||||||
}
|
|
||||||
|
|
||||||
return x509.NewCertPool()
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
26
vendor/github.com/go-acme/lego/v4/log/logger.go
generated
vendored
26
vendor/github.com/go-acme/lego/v4/log/logger.go
generated
vendored
|
|
@ -10,50 +10,50 @@ var Logger StdLogger = log.New(os.Stderr, "", log.LstdFlags)
|
||||||
|
|
||||||
// StdLogger interface for Standard Logger.
|
// StdLogger interface for Standard Logger.
|
||||||
type StdLogger interface {
|
type StdLogger interface {
|
||||||
Fatal(args ...any)
|
Fatal(args ...interface{})
|
||||||
Fatalln(args ...any)
|
Fatalln(args ...interface{})
|
||||||
Fatalf(format string, args ...any)
|
Fatalf(format string, args ...interface{})
|
||||||
Print(args ...any)
|
Print(args ...interface{})
|
||||||
Println(args ...any)
|
Println(args ...interface{})
|
||||||
Printf(format string, args ...any)
|
Printf(format string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatal writes a log entry.
|
// Fatal writes a log entry.
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||||
func Fatal(args ...any) {
|
func Fatal(args ...interface{}) {
|
||||||
Logger.Fatal(args...)
|
Logger.Fatal(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fatalf writes a log entry.
|
// Fatalf writes a log entry.
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||||
func Fatalf(format string, args ...any) {
|
func Fatalf(format string, args ...interface{}) {
|
||||||
Logger.Fatalf(format, args...)
|
Logger.Fatalf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print writes a log entry.
|
// Print writes a log entry.
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||||
func Print(args ...any) {
|
func Print(args ...interface{}) {
|
||||||
Logger.Print(args...)
|
Logger.Print(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Println writes a log entry.
|
// Println writes a log entry.
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||||
func Println(args ...any) {
|
func Println(args ...interface{}) {
|
||||||
Logger.Println(args...)
|
Logger.Println(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Printf writes a log entry.
|
// Printf writes a log entry.
|
||||||
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
// It uses Logger if not nil, otherwise it uses the default log.Logger.
|
||||||
func Printf(format string, args ...any) {
|
func Printf(format string, args ...interface{}) {
|
||||||
Logger.Printf(format, args...)
|
Logger.Printf(format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Warnf writes a log entry.
|
// Warnf writes a log entry.
|
||||||
func Warnf(format string, args ...any) {
|
func Warnf(format string, args ...interface{}) {
|
||||||
Printf("[WARN] "+format, args...)
|
Printf("[WARN] "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Infof writes a log entry.
|
// Infof writes a log entry.
|
||||||
func Infof(format string, args ...any) {
|
func Infof(format string, args ...interface{}) {
|
||||||
Printf("[INFO] "+format, args...)
|
Printf("[INFO] "+format, args...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
115
vendor/github.com/go-acme/lego/v4/platform/config/env/env.go
generated
vendored
115
vendor/github.com/go-acme/lego/v4/platform/config/env/env.go
generated
vendored
|
|
@ -3,6 +3,7 @@ package env
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -16,13 +17,11 @@ func Get(names ...string) (map[string]string, error) {
|
||||||
values := map[string]string{}
|
values := map[string]string{}
|
||||||
|
|
||||||
var missingEnvVars []string
|
var missingEnvVars []string
|
||||||
|
|
||||||
for _, envVar := range names {
|
for _, envVar := range names {
|
||||||
value := GetOrFile(envVar)
|
value := GetOrFile(envVar)
|
||||||
if value == "" {
|
if value == "" {
|
||||||
missingEnvVars = append(missingEnvVars, envVar)
|
missingEnvVars = append(missingEnvVars, envVar)
|
||||||
}
|
}
|
||||||
|
|
||||||
values[envVar] = value
|
values[envVar] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,11 +55,11 @@ func Get(names ...string) (map[string]string, error) {
|
||||||
// // LEGO_TWO=""
|
// // LEGO_TWO=""
|
||||||
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
// env.GetWithFallback([]string{"LEGO_ONE", "LEGO_TWO"})
|
||||||
// // => error
|
// // => error
|
||||||
|
//
|
||||||
func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
||||||
values := map[string]string{}
|
values := map[string]string{}
|
||||||
|
|
||||||
var missingEnvVars []string
|
var missingEnvVars []string
|
||||||
|
|
||||||
for _, names := range groups {
|
for _, names := range groups {
|
||||||
if len(names) == 0 {
|
if len(names) == 0 {
|
||||||
return nil, errors.New("undefined environment variable names")
|
return nil, errors.New("undefined environment variable names")
|
||||||
|
|
@ -71,7 +70,6 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
||||||
missingEnvVars = append(missingEnvVars, envVar)
|
missingEnvVars = append(missingEnvVars, envVar)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
values[envVar] = value
|
values[envVar] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,26 +80,15 @@ func GetWithFallback(groups ...[]string) (map[string]string, error) {
|
||||||
return values, nil
|
return values, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetOneWithFallback[T any](main string, defaultValue T, fn func(string) (T, error), names ...string) T {
|
|
||||||
v, _ := getOneWithFallback(main, names...)
|
|
||||||
|
|
||||||
value, err := fn(v)
|
|
||||||
if err != nil {
|
|
||||||
return defaultValue
|
|
||||||
}
|
|
||||||
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOneWithFallback(main string, names ...string) (string, string) {
|
func getOneWithFallback(main string, names ...string) (string, string) {
|
||||||
value := GetOrFile(main)
|
value := GetOrFile(main)
|
||||||
if value != "" {
|
if len(value) > 0 {
|
||||||
return value, main
|
return value, main
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
value := GetOrFile(name)
|
value := GetOrFile(name)
|
||||||
if value != "" {
|
if len(value) > 0 {
|
||||||
return value, main
|
return value, main
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -109,32 +96,43 @@ func getOneWithFallback(main string, names ...string) (string, string) {
|
||||||
return "", main
|
return "", main
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetOrDefaultInt returns the given environment variable value as an integer.
|
||||||
|
// Returns the default if the envvar cannot be coopered to an int, or is not found.
|
||||||
|
func GetOrDefaultInt(envVar string, defaultValue int) int {
|
||||||
|
v, err := strconv.Atoi(GetOrFile(envVar))
|
||||||
|
if err != nil {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOrDefaultSecond returns the given environment variable value as an time.Duration (second).
|
||||||
|
// Returns the default if the envvar cannot be coopered to an int, or is not found.
|
||||||
|
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
|
||||||
|
v := GetOrDefaultInt(envVar, -1)
|
||||||
|
if v < 0 {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Duration(v) * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// GetOrDefaultString returns the given environment variable value as a string.
|
// GetOrDefaultString returns the given environment variable value as a string.
|
||||||
// Returns the default if the env var cannot be found.
|
// Returns the default if the envvar cannot be find.
|
||||||
func GetOrDefaultString(envVar, defaultValue string) string {
|
func GetOrDefaultString(envVar, defaultValue string) string {
|
||||||
return getOrDefault(envVar, defaultValue, ParseString)
|
v := GetOrFile(envVar)
|
||||||
|
if v == "" {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return v
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetOrDefaultBool returns the given environment variable value as a boolean.
|
// GetOrDefaultBool returns the given environment variable value as a boolean.
|
||||||
// Returns the default if the env var cannot be coopered to a boolean, or is not found.
|
// Returns the default if the envvar cannot be coopered to a boolean, or is not found.
|
||||||
func GetOrDefaultBool(envVar string, defaultValue bool) bool {
|
func GetOrDefaultBool(envVar string, defaultValue bool) bool {
|
||||||
return getOrDefault(envVar, defaultValue, strconv.ParseBool)
|
v, err := strconv.ParseBool(GetOrFile(envVar))
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrDefaultInt returns the given environment variable value as an integer.
|
|
||||||
// Returns the default if the env var cannot be coopered to an int, or is not found.
|
|
||||||
func GetOrDefaultInt(envVar string, defaultValue int) int {
|
|
||||||
return getOrDefault(envVar, defaultValue, strconv.Atoi)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetOrDefaultSecond returns the given environment variable value as a time.Duration (second).
|
|
||||||
// Returns the default if the env var cannot be coopered to an int, or is not found.
|
|
||||||
func GetOrDefaultSecond(envVar string, defaultValue time.Duration) time.Duration {
|
|
||||||
return getOrDefault(envVar, defaultValue, ParseSecond)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getOrDefault[T any](envVar string, defaultValue T, fn func(string) (T, error)) T {
|
|
||||||
v, err := fn(GetOrFile(envVar))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return defaultValue
|
return defaultValue
|
||||||
}
|
}
|
||||||
|
|
@ -152,13 +150,12 @@ func GetOrFile(envVar string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
fileVar := envVar + "_FILE"
|
fileVar := envVar + "_FILE"
|
||||||
|
|
||||||
fileVarValue := os.Getenv(fileVar)
|
fileVarValue := os.Getenv(fileVar)
|
||||||
if fileVarValue == "" {
|
if fileVarValue == "" {
|
||||||
return envVarValue
|
return envVarValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fileContents, err := os.ReadFile(fileVarValue)
|
fileContents, err := ioutil.ReadFile(fileVarValue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
log.Printf("Failed to read the file %s (defined by env var %s): %s", fileVarValue, fileVar, err)
|
||||||
return ""
|
return ""
|
||||||
|
|
@ -166,43 +163,3 @@ func GetOrFile(envVar string) string {
|
||||||
|
|
||||||
return strings.TrimSuffix(string(fileContents), "\n")
|
return strings.TrimSuffix(string(fileContents), "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseSecond parses env var value (string) to a second (time.Duration).
|
|
||||||
func ParseSecond(s string) (time.Duration, error) {
|
|
||||||
v, err := strconv.Atoi(s)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if v < 0 {
|
|
||||||
return 0, fmt.Errorf("unsupported value: %d", v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return time.Duration(v) * time.Second, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseString parses env var value (string) to a string but throws an error when the string is empty.
|
|
||||||
func ParseString(s string) (string, error) {
|
|
||||||
if s == "" {
|
|
||||||
return "", errors.New("empty string")
|
|
||||||
}
|
|
||||||
|
|
||||||
return s, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParsePairs parses a raw string of comma-separated key-value pairs into a map.
|
|
||||||
// Keys and values are separated by a colon and are trimmed of whitespace.
|
|
||||||
func ParsePairs(raw string) (map[string]string, error) {
|
|
||||||
result := make(map[string]string)
|
|
||||||
|
|
||||||
for pair := range strings.SplitSeq(strings.TrimSuffix(raw, ","), ",") {
|
|
||||||
data := strings.Split(pair, ":")
|
|
||||||
if len(data) != 2 {
|
|
||||||
return nil, fmt.Errorf("incorrect pair: %s", pair)
|
|
||||||
}
|
|
||||||
|
|
||||||
result[strings.TrimSpace(data[0])] = strings.TrimSpace(data[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
|
||||||
23
vendor/github.com/go-acme/lego/v4/platform/wait/wait.go
generated
vendored
23
vendor/github.com/go-acme/lego/v4/platform/wait/wait.go
generated
vendored
|
|
@ -1,11 +1,10 @@
|
||||||
package wait
|
package wait
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cenkalti/backoff/v5"
|
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -14,25 +13,21 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er
|
||||||
log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
|
log.Infof("Wait for %s [timeout: %s, interval: %s]", msg, timeout, interval)
|
||||||
|
|
||||||
var lastErr error
|
var lastErr error
|
||||||
|
|
||||||
timeUp := time.After(timeout)
|
timeUp := time.After(timeout)
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-timeUp:
|
case <-timeUp:
|
||||||
if lastErr == nil {
|
if lastErr == nil {
|
||||||
return fmt.Errorf("%s: time limit exceeded", msg)
|
return errors.New("time limit exceeded")
|
||||||
}
|
}
|
||||||
|
return fmt.Errorf("time limit exceeded: last error: %w", lastErr)
|
||||||
return fmt.Errorf("%s: time limit exceeded: last error: %w", msg, lastErr)
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
stop, err := f()
|
stop, err := f()
|
||||||
if stop {
|
if stop {
|
||||||
return err
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lastErr = err
|
lastErr = err
|
||||||
}
|
}
|
||||||
|
|
@ -40,13 +35,3 @@ func For(msg string, timeout, interval time.Duration, f func() (bool, error)) er
|
||||||
time.Sleep(interval)
|
time.Sleep(interval)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retry retries the given operation until it succeeds or the context is canceled.
|
|
||||||
// Similar to [backoff.Retry] but with a different signature.
|
|
||||||
func Retry(ctx context.Context, operation func() error, opts ...backoff.RetryOption) error {
|
|
||||||
_, err := backoff.Retry(ctx, func() (any, error) {
|
|
||||||
return nil, operation()
|
|
||||||
}, opts...)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
|
||||||
1
vendor/github.com/go-acme/lego/v4/providers/dns/internal/clientdebug/.gitattributes
generated
vendored
1
vendor/github.com/go-acme/lego/v4/providers/dns/internal/clientdebug/.gitattributes
generated
vendored
|
|
@ -1 +0,0 @@
|
||||||
/testdata/** text eol=lf
|
|
||||||
134
vendor/github.com/go-acme/lego/v4/providers/dns/internal/clientdebug/client.go
generated
vendored
134
vendor/github.com/go-acme/lego/v4/providers/dns/internal/clientdebug/client.go
generated
vendored
|
|
@ -1,134 +0,0 @@
|
||||||
package clientdebug
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/http/httputil"
|
|
||||||
"os"
|
|
||||||
"regexp"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/platform/config/env"
|
|
||||||
)
|
|
||||||
|
|
||||||
const replacement = "***"
|
|
||||||
|
|
||||||
type Option func(*DumpTransport)
|
|
||||||
|
|
||||||
func WithEnvKeys(keys ...string) Option {
|
|
||||||
return func(d *DumpTransport) {
|
|
||||||
for _, key := range keys {
|
|
||||||
v := strings.TrimSpace(env.GetOrFile(key))
|
|
||||||
if v == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
d.replacements = append(d.replacements, v, replacement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithValues(values ...string) Option {
|
|
||||||
return func(d *DumpTransport) {
|
|
||||||
for _, value := range values {
|
|
||||||
d.replacements = append(d.replacements, value, replacement)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func WithHeaders(keys ...string) Option {
|
|
||||||
return func(d *DumpTransport) {
|
|
||||||
d.regexps = append(d.regexps,
|
|
||||||
regexp.MustCompile(fmt.Sprintf(`(?im)^(%s):.+$`, strings.Join(keys, "|"))))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type DumpTransport struct {
|
|
||||||
rt http.RoundTripper
|
|
||||||
|
|
||||||
replacements []string
|
|
||||||
replacer *strings.Replacer
|
|
||||||
|
|
||||||
regexps []*regexp.Regexp
|
|
||||||
|
|
||||||
writer io.Writer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewDumpTransport(rt http.RoundTripper, opts ...Option) *DumpTransport {
|
|
||||||
if rt == nil {
|
|
||||||
rt = http.DefaultTransport
|
|
||||||
}
|
|
||||||
|
|
||||||
d := &DumpTransport{
|
|
||||||
rt: rt,
|
|
||||||
writer: os.Stdout,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(d)
|
|
||||||
}
|
|
||||||
|
|
||||||
d.regexps = append(d.regexps,
|
|
||||||
regexp.MustCompile(`(?im)^(Authorization):.+$`),
|
|
||||||
regexp.MustCompile(`(?im)^(Token|X-Token):.+$`),
|
|
||||||
regexp.MustCompile(`(?im)^(Auth-Token|X-Auth-Token):.+$`),
|
|
||||||
regexp.MustCompile(`(?im)^(Api-Key|X-Api-Key|X-Api-Secret):.+$`),
|
|
||||||
)
|
|
||||||
|
|
||||||
if len(d.replacements) > 0 {
|
|
||||||
d.replacer = strings.NewReplacer(d.replacements...)
|
|
||||||
}
|
|
||||||
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DumpTransport) RoundTrip(h *http.Request) (*http.Response, error) {
|
|
||||||
data, _ := httputil.DumpRequestOut(h, true)
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(d.writer, "[HTTP Request]")
|
|
||||||
_, _ = fmt.Fprintln(d.writer, d.redact(data))
|
|
||||||
|
|
||||||
resp, err := d.rt.RoundTrip(h)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
data, _ = httputil.DumpResponse(resp, true)
|
|
||||||
|
|
||||||
_, _ = fmt.Fprintln(d.writer, "[HTTP Response]")
|
|
||||||
_, _ = fmt.Fprintln(d.writer, d.redact(data))
|
|
||||||
|
|
||||||
return resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (d *DumpTransport) redact(content []byte) string {
|
|
||||||
data := string(content)
|
|
||||||
|
|
||||||
for _, r := range d.regexps {
|
|
||||||
data = r.ReplaceAllString(data, "$1: "+replacement)
|
|
||||||
}
|
|
||||||
|
|
||||||
if d.replacer == nil {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
|
|
||||||
return d.replacer.Replace(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wrap wraps an HTTP client Transport with the [DumpTransport].
|
|
||||||
func Wrap(client *http.Client, opts ...Option) *http.Client {
|
|
||||||
val, found := os.LookupEnv("LEGO_DEBUG_DNS_API_HTTP_CLIENT")
|
|
||||||
if !found {
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
if ok, _ := strconv.ParseBool(val); !ok {
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
|
|
||||||
client.Transport = NewDumpTransport(client.Transport, opts...)
|
|
||||||
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
133
vendor/github.com/go-acme/lego/v4/providers/dns/internal/errutils/client.go
generated
vendored
133
vendor/github.com/go-acme/lego/v4/providers/dns/internal/errutils/client.go
generated
vendored
|
|
@ -1,133 +0,0 @@
|
||||||
package errutils
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
const legoDebugClientVerboseError = "LEGO_DEBUG_CLIENT_VERBOSE_ERROR"
|
|
||||||
|
|
||||||
// HTTPDoError uses with `(http.Client).Do` error.
|
|
||||||
type HTTPDoError struct {
|
|
||||||
req *http.Request
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewHTTPDoError creates a new HTTPDoError.
|
|
||||||
func NewHTTPDoError(req *http.Request, err error) *HTTPDoError {
|
|
||||||
return &HTTPDoError{req: req, err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h HTTPDoError) Error() string {
|
|
||||||
msg := "unable to communicate with the API server:"
|
|
||||||
|
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
|
||||||
msg += fmt.Sprintf(" [request: %s %s]", h.req.Method, h.req.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
if h.err == nil {
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg + fmt.Sprintf(" error: %v", h.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h HTTPDoError) Unwrap() error {
|
|
||||||
return h.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReadResponseError use with `io.ReadAll` when reading response body.
|
|
||||||
type ReadResponseError struct {
|
|
||||||
req *http.Request
|
|
||||||
StatusCode int
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewReadResponseError creates a new ReadResponseError.
|
|
||||||
func NewReadResponseError(req *http.Request, statusCode int, err error) *ReadResponseError {
|
|
||||||
return &ReadResponseError{req: req, StatusCode: statusCode, err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ReadResponseError) Error() string {
|
|
||||||
msg := "unable to read response body:"
|
|
||||||
|
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
|
||||||
msg += fmt.Sprintf(" [request: %s %s]", r.req.Method, r.req.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg += fmt.Sprintf(" [status code: %d]", r.StatusCode)
|
|
||||||
|
|
||||||
if r.err == nil {
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg + fmt.Sprintf(" error: %v", r.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r ReadResponseError) Unwrap() error {
|
|
||||||
return r.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalError uses with `json.Unmarshal` or `xml.Unmarshal` when reading response body.
|
|
||||||
type UnmarshalError struct {
|
|
||||||
req *http.Request
|
|
||||||
StatusCode int
|
|
||||||
Body []byte
|
|
||||||
err error
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnmarshalError creates a new UnmarshalError.
|
|
||||||
func NewUnmarshalError(req *http.Request, statusCode int, body []byte, err error) *UnmarshalError {
|
|
||||||
return &UnmarshalError{req: req, StatusCode: statusCode, Body: bytes.TrimSpace(body), err: err}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UnmarshalError) Error() string {
|
|
||||||
msg := "unable to unmarshal response:"
|
|
||||||
|
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
|
||||||
msg += fmt.Sprintf(" [request: %s %s]", u.req.Method, u.req.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
msg += fmt.Sprintf(" [status code: %d] body: %s", u.StatusCode, string(u.Body))
|
|
||||||
|
|
||||||
if u.err == nil {
|
|
||||||
return msg
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg + fmt.Sprintf(" error: %v", u.err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UnmarshalError) Unwrap() error {
|
|
||||||
return u.err
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnexpectedStatusCodeError use when the status of the response is unexpected but there is no API error type.
|
|
||||||
type UnexpectedStatusCodeError struct {
|
|
||||||
req *http.Request
|
|
||||||
StatusCode int
|
|
||||||
Body []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewUnexpectedStatusCodeError creates a new UnexpectedStatusCodeError.
|
|
||||||
func NewUnexpectedStatusCodeError(req *http.Request, statusCode int, body []byte) *UnexpectedStatusCodeError {
|
|
||||||
return &UnexpectedStatusCodeError{req: req, StatusCode: statusCode, Body: bytes.TrimSpace(body)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewUnexpectedResponseStatusCodeError(req *http.Request, resp *http.Response) *UnexpectedStatusCodeError {
|
|
||||||
raw, _ := io.ReadAll(resp.Body)
|
|
||||||
return &UnexpectedStatusCodeError{req: req, StatusCode: resp.StatusCode, Body: bytes.TrimSpace(raw)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u UnexpectedStatusCodeError) Error() string {
|
|
||||||
msg := "unexpected status code:"
|
|
||||||
|
|
||||||
if ok, _ := strconv.ParseBool(os.Getenv(legoDebugClientVerboseError)); ok {
|
|
||||||
msg += fmt.Sprintf(" [request: %s %s]", u.req.Method, u.req.URL)
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg + fmt.Sprintf(" [status code: %d] body: %s", u.StatusCode, string(u.Body))
|
|
||||||
}
|
|
||||||
29
vendor/github.com/go-acme/lego/v4/providers/dns/internal/useragent/useragent.go
generated
vendored
29
vendor/github.com/go-acme/lego/v4/providers/dns/internal/useragent/useragent.go
generated
vendored
|
|
@ -1,29 +0,0 @@
|
||||||
// Code generated by 'internal/releaser'; DO NOT EDIT.
|
|
||||||
|
|
||||||
package useragent
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"runtime"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// ourUserAgent is the User-Agent of this underlying library package.
|
|
||||||
ourUserAgent = "goacme-lego/4.29.0"
|
|
||||||
|
|
||||||
// ourUserAgentComment is part of the UA comment linked to the version status of this underlying library package.
|
|
||||||
// values: detach|release
|
|
||||||
// NOTE: Update this with each tagged release.
|
|
||||||
ourUserAgentComment = "release"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Get builds and returns the User-Agent string.
|
|
||||||
func Get() string {
|
|
||||||
return fmt.Sprintf("%s (%s; %s; %s)", ourUserAgent, ourUserAgentComment, runtime.GOOS, runtime.GOARCH)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetHeader sets the User-Agent header.
|
|
||||||
func SetHeader(h http.Header) {
|
|
||||||
h.Set("User-Agent", Get())
|
|
||||||
}
|
|
||||||
156
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.go
generated
vendored
156
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.go
generated
vendored
|
|
@ -5,26 +5,26 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||||
"github.com/go-acme/lego/v4/platform/config/env"
|
"github.com/go-acme/lego/v4/platform/config/env"
|
||||||
"github.com/go-acme/lego/v4/providers/dns/internal/clientdebug"
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/internal/useragent"
|
|
||||||
"github.com/ovh/go-ovh/ovh"
|
"github.com/ovh/go-ovh/ovh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OVH API reference: https://eu.api.ovh.com/
|
// OVH API reference: https://eu.api.ovh.com/
|
||||||
// Create a Token: https://eu.api.ovh.com/createToken/
|
// Create a Token: https://eu.api.ovh.com/createToken/
|
||||||
// Create a OAuth2 client: https://eu.api.ovh.com/console/?section=%2Fme&branch=v1#post-/me/api/oauth2/client
|
|
||||||
|
|
||||||
// Environment variables names.
|
// Environment variables names.
|
||||||
const (
|
const (
|
||||||
envNamespace = "OVH_"
|
envNamespace = "OVH_"
|
||||||
|
|
||||||
EnvEndpoint = envNamespace + "ENDPOINT"
|
EnvEndpoint = envNamespace + "ENDPOINT"
|
||||||
|
EnvApplicationKey = envNamespace + "APPLICATION_KEY"
|
||||||
|
EnvApplicationSecret = envNamespace + "APPLICATION_SECRET"
|
||||||
|
EnvConsumerKey = envNamespace + "CONSUMER_KEY"
|
||||||
|
|
||||||
EnvTTL = envNamespace + "TTL"
|
EnvTTL = envNamespace + "TTL"
|
||||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||||
|
|
@ -32,24 +32,6 @@ const (
|
||||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Authenticate using application key.
|
|
||||||
const (
|
|
||||||
EnvApplicationKey = envNamespace + "APPLICATION_KEY"
|
|
||||||
EnvApplicationSecret = envNamespace + "APPLICATION_SECRET"
|
|
||||||
EnvConsumerKey = envNamespace + "CONSUMER_KEY"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Authenticate using OAuth2 client.
|
|
||||||
const (
|
|
||||||
EnvClientID = envNamespace + "CLIENT_ID"
|
|
||||||
EnvClientSecret = envNamespace + "CLIENT_SECRET"
|
|
||||||
)
|
|
||||||
|
|
||||||
// EnvAccessToken Authenticate using Access Token client.
|
|
||||||
const EnvAccessToken = envNamespace + "ACCESS_TOKEN"
|
|
||||||
|
|
||||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
|
||||||
|
|
||||||
// Record a DNS record.
|
// Record a DNS record.
|
||||||
type Record struct {
|
type Record struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
|
|
@ -60,24 +42,12 @@ type Record struct {
|
||||||
Zone string `json:"zone,omitempty"`
|
Zone string `json:"zone,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OAuth2Config the OAuth2 specific configuration.
|
|
||||||
type OAuth2Config struct {
|
|
||||||
ClientID string
|
|
||||||
ClientSecret string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Config is used to configure the creation of the DNSProvider.
|
// Config is used to configure the creation of the DNSProvider.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
APIEndpoint string
|
APIEndpoint string
|
||||||
|
|
||||||
ApplicationKey string
|
ApplicationKey string
|
||||||
ApplicationSecret string
|
ApplicationSecret string
|
||||||
ConsumerKey string
|
ConsumerKey string
|
||||||
|
|
||||||
OAuth2Config *OAuth2Config
|
|
||||||
|
|
||||||
AccessToken string
|
|
||||||
|
|
||||||
PropagationTimeout time.Duration
|
PropagationTimeout time.Duration
|
||||||
PollingInterval time.Duration
|
PollingInterval time.Duration
|
||||||
TTL int
|
TTL int
|
||||||
|
|
@ -96,10 +66,6 @@ func NewDefaultConfig() *Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Config) hasAppKeyAuth() bool {
|
|
||||||
return c.ApplicationKey != "" || c.ApplicationSecret != "" || c.ConsumerKey != ""
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSProvider implements the challenge.Provider interface.
|
// DNSProvider implements the challenge.Provider interface.
|
||||||
type DNSProvider struct {
|
type DNSProvider struct {
|
||||||
config *Config
|
config *Config
|
||||||
|
|
@ -112,26 +78,16 @@ type DNSProvider struct {
|
||||||
// Credentials must be passed in the environment variables:
|
// Credentials must be passed in the environment variables:
|
||||||
// OVH_ENDPOINT (must be either "ovh-eu" or "ovh-ca"), OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY.
|
// OVH_ENDPOINT (must be either "ovh-eu" or "ovh-ca"), OVH_APPLICATION_KEY, OVH_APPLICATION_SECRET, OVH_CONSUMER_KEY.
|
||||||
func NewDNSProvider() (*DNSProvider, error) {
|
func NewDNSProvider() (*DNSProvider, error) {
|
||||||
|
values, err := env.Get(EnvEndpoint, EnvApplicationKey, EnvApplicationSecret, EnvConsumerKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ovh: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
config := NewDefaultConfig()
|
config := NewDefaultConfig()
|
||||||
|
config.APIEndpoint = values[EnvEndpoint]
|
||||||
// https://github.com/ovh/go-ovh/blob/6817886d12a8c5650794b28da635af9fcdfd1162/ovh/configuration.go#L105
|
config.ApplicationKey = values[EnvApplicationKey]
|
||||||
config.APIEndpoint = env.GetOrDefaultString(EnvEndpoint, "ovh-eu")
|
config.ApplicationSecret = values[EnvApplicationSecret]
|
||||||
|
config.ConsumerKey = values[EnvConsumerKey]
|
||||||
config.ApplicationKey = env.GetOrFile(EnvApplicationKey)
|
|
||||||
config.ApplicationSecret = env.GetOrFile(EnvApplicationSecret)
|
|
||||||
config.ConsumerKey = env.GetOrFile(EnvConsumerKey)
|
|
||||||
|
|
||||||
config.AccessToken = env.GetOrFile(EnvAccessToken)
|
|
||||||
|
|
||||||
clientID := env.GetOrFile(EnvClientID)
|
|
||||||
clientSecret := env.GetOrFile(EnvClientSecret)
|
|
||||||
|
|
||||||
if clientID != "" || clientSecret != "" {
|
|
||||||
config.OAuth2Config = &OAuth2Config{
|
|
||||||
ClientID: clientID,
|
|
||||||
ClientSecret: clientSecret,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return NewDNSProviderConfig(config)
|
return NewDNSProviderConfig(config)
|
||||||
}
|
}
|
||||||
|
|
@ -142,27 +98,22 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
return nil, errors.New("ovh: the configuration of the DNS provider is nil")
|
return nil, errors.New("ovh: the configuration of the DNS provider is nil")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.OAuth2Config != nil && config.hasAppKeyAuth() && config.AccessToken != "" {
|
if config.APIEndpoint == "" || config.ApplicationKey == "" || config.ApplicationSecret == "" || config.ConsumerKey == "" {
|
||||||
return nil, errors.New("ovh: can't use multiple authentication systems (ApplicationKey, OAuth2, Access Token)")
|
return nil, errors.New("ovh: credentials missing")
|
||||||
}
|
}
|
||||||
|
|
||||||
if config.OAuth2Config != nil && config.AccessToken != "" {
|
client, err := ovh.NewClient(
|
||||||
return nil, errors.New("ovh: can't use multiple authentication systems (OAuth2, Access Token)")
|
config.APIEndpoint,
|
||||||
}
|
config.ApplicationKey,
|
||||||
|
config.ApplicationSecret,
|
||||||
if config.OAuth2Config != nil && config.hasAppKeyAuth() {
|
config.ConsumerKey,
|
||||||
return nil, errors.New("ovh: can't use multiple authentication systems (ApplicationKey, OAuth2)")
|
)
|
||||||
}
|
|
||||||
|
|
||||||
if config.hasAppKeyAuth() && config.AccessToken != "" {
|
|
||||||
return nil, errors.New("ovh: can't use multiple authentication systems (ApplicationKey, Access Token)")
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := newClient(config)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("ovh: %w", err)
|
return nil, fmt.Errorf("ovh: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
client.Client = config.HTTPClient
|
||||||
|
|
||||||
return &DNSProvider{
|
return &DNSProvider{
|
||||||
config: config,
|
config: config,
|
||||||
client: client,
|
client: client,
|
||||||
|
|
@ -172,26 +123,22 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||||
|
|
||||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
// Present creates a TXT record to fulfill the dns-01 challenge.
|
||||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
fqdn, value := dns01.GetRecord(domain, keyAuth)
|
||||||
|
|
||||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
// Parse domain name
|
||||||
|
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: could not find zone for domain %q: %w", domain, err)
|
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authZone = dns01.UnFqdn(authZone)
|
authZone = dns01.UnFqdn(authZone)
|
||||||
|
subDomain := extractRecordName(fqdn, authZone)
|
||||||
subDomain, err := dns01.ExtractSubDomain(info.EffectiveFQDN, authZone)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("ovh: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
|
reqURL := fmt.Sprintf("/domain/zone/%s/record", authZone)
|
||||||
reqData := Record{FieldType: "TXT", SubDomain: subDomain, Target: info.Value, TTL: d.config.TTL}
|
reqData := Record{FieldType: "TXT", SubDomain: subDomain, Target: value, TTL: d.config.TTL}
|
||||||
|
|
||||||
// Create TXT record
|
// Create TXT record
|
||||||
var respData Record
|
var respData Record
|
||||||
|
|
||||||
err = d.client.Post(reqURL, reqData, &respData)
|
err = d.client.Post(reqURL, reqData, &respData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: error when call api to add record (%s): %w", reqURL, err)
|
return fmt.Errorf("ovh: error when call api to add record (%s): %w", reqURL, err)
|
||||||
|
|
@ -199,7 +146,6 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
// Apply the change
|
// Apply the change
|
||||||
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
||||||
|
|
||||||
err = d.client.Post(reqURL, nil, nil)
|
err = d.client.Post(reqURL, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err)
|
return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err)
|
||||||
|
|
@ -214,20 +160,19 @@ func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters.
|
// CleanUp removes the TXT record matching the specified parameters.
|
||||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
fqdn, _ := dns01.GetRecord(domain, keyAuth)
|
||||||
|
|
||||||
// get the record's unique ID from when we created it
|
// get the record's unique ID from when we created it
|
||||||
d.recordIDsMu.Lock()
|
d.recordIDsMu.Lock()
|
||||||
recordID, ok := d.recordIDs[token]
|
recordID, ok := d.recordIDs[token]
|
||||||
d.recordIDsMu.Unlock()
|
d.recordIDsMu.Unlock()
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("ovh: unknown record ID for '%s'", info.EffectiveFQDN)
|
return fmt.Errorf("ovh: unknown record ID for '%s'", fqdn)
|
||||||
}
|
}
|
||||||
|
|
||||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
authZone, err := dns01.FindZoneByFqdn(dns01.ToFqdn(domain))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: could not find zone for domain %q: %w", domain, err)
|
return fmt.Errorf("ovh: could not determine zone for domain %q: %w", domain, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
authZone = dns01.UnFqdn(authZone)
|
authZone = dns01.UnFqdn(authZone)
|
||||||
|
|
@ -241,7 +186,6 @@ func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||||
|
|
||||||
// Apply the change
|
// Apply the change
|
||||||
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
reqURL = fmt.Sprintf("/domain/zone/%s/refresh", authZone)
|
||||||
|
|
||||||
err = d.client.Post(reqURL, nil, nil)
|
err = d.client.Post(reqURL, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err)
|
return fmt.Errorf("ovh: error when call api to refresh zone (%s): %w", reqURL, err)
|
||||||
|
|
@ -261,34 +205,10 @@ func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||||
}
|
}
|
||||||
|
|
||||||
func newClient(config *Config) (*ovh.Client, error) {
|
func extractRecordName(fqdn, zone string) string {
|
||||||
var (
|
name := dns01.UnFqdn(fqdn)
|
||||||
client *ovh.Client
|
if idx := strings.Index(name, "."+zone); idx != -1 {
|
||||||
err error
|
return name[:idx]
|
||||||
)
|
|
||||||
|
|
||||||
switch {
|
|
||||||
case config.hasAppKeyAuth():
|
|
||||||
client, err = ovh.NewClient(config.APIEndpoint, config.ApplicationKey, config.ApplicationSecret, config.ConsumerKey)
|
|
||||||
case config.OAuth2Config != nil:
|
|
||||||
client, err = ovh.NewOAuth2Client(config.APIEndpoint, config.OAuth2Config.ClientID, config.OAuth2Config.ClientSecret)
|
|
||||||
case config.AccessToken != "":
|
|
||||||
client, err = ovh.NewAccessTokenClient(config.APIEndpoint, config.AccessToken)
|
|
||||||
default:
|
|
||||||
client, err = ovh.NewDefaultClient()
|
|
||||||
}
|
}
|
||||||
|
return name
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("new client: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client.UserAgent = useragent.Get()
|
|
||||||
|
|
||||||
if config.HTTPClient != nil {
|
|
||||||
client.Client = config.HTTPClient
|
|
||||||
}
|
|
||||||
|
|
||||||
client.Client = clientdebug.Wrap(client.Client)
|
|
||||||
|
|
||||||
return client, nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.toml
generated
vendored
52
vendor/github.com/go-acme/lego/v4/providers/dns/ovh/ovh.toml
generated
vendored
|
|
@ -5,26 +5,11 @@ Code = "ovh"
|
||||||
Since = "v0.4.0"
|
Since = "v0.4.0"
|
||||||
|
|
||||||
Example = '''
|
Example = '''
|
||||||
# Application Key authentication:
|
|
||||||
|
|
||||||
OVH_APPLICATION_KEY=1234567898765432 \
|
OVH_APPLICATION_KEY=1234567898765432 \
|
||||||
OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \
|
OVH_APPLICATION_SECRET=b9841238feb177a84330febba8a832089 \
|
||||||
OVH_CONSUMER_KEY=256vfsd347245sdfg \
|
OVH_CONSUMER_KEY=256vfsd347245sdfg \
|
||||||
OVH_ENDPOINT=ovh-eu \
|
OVH_ENDPOINT=ovh-eu \
|
||||||
lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run
|
lego --email myemail@example.com --dns ovh --domains my.example.org run
|
||||||
|
|
||||||
# Or Access Token:
|
|
||||||
|
|
||||||
OVH_ACCESS_TOKEN=xxx \
|
|
||||||
OVH_ENDPOINT=ovh-eu \
|
|
||||||
lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run
|
|
||||||
|
|
||||||
# Or OAuth2:
|
|
||||||
|
|
||||||
OVH_CLIENT_ID=yyy \
|
|
||||||
OVH_CLIENT_SECRET=xxx \
|
|
||||||
OVH_ENDPOINT=ovh-eu \
|
|
||||||
lego --email you@example.com --dns ovh -d '*.example.com' -d example.com run
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
Additional = '''
|
Additional = '''
|
||||||
|
|
@ -32,7 +17,7 @@ Additional = '''
|
||||||
|
|
||||||
Application key and secret can be created by following the [OVH guide](https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/).
|
Application key and secret can be created by following the [OVH guide](https://docs.ovh.com/gb/en/customer/first-steps-with-ovh-api/).
|
||||||
|
|
||||||
When requesting the consumer key, the following configuration can be used to define access rights:
|
When requesting the consumer key, the following configuration can be use to define access rights:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
@ -48,38 +33,19 @@ When requesting the consumer key, the following configuration can be used to def
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
## OAuth2 Client Credentials
|
|
||||||
|
|
||||||
Another method for authentication is by using OAuth2 client credentials.
|
|
||||||
|
|
||||||
An IAM policy and service account can be created by following the [OVH guide](https://help.ovhcloud.com/csm/en-manage-service-account?id=kb_article_view&sysparm_article=KB0059343).
|
|
||||||
|
|
||||||
Following IAM policies need to be authorized for the affected domain:
|
|
||||||
|
|
||||||
* dnsZone:apiovh:record/create
|
|
||||||
* dnsZone:apiovh:record/delete
|
|
||||||
* dnsZone:apiovh:refresh
|
|
||||||
|
|
||||||
## Important Note
|
|
||||||
|
|
||||||
Both authentication methods cannot be used at the same time.
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
[Configuration]
|
[Configuration]
|
||||||
[Configuration.Credentials]
|
[Configuration.Credentials]
|
||||||
OVH_ENDPOINT = "Endpoint URL (ovh-eu or ovh-ca)"
|
OVH_ENDPOINT = "Endpoint URL (ovh-eu or ovh-ca)"
|
||||||
OVH_APPLICATION_KEY = "Application key (Application Key authentication)"
|
OVH_APPLICATION_KEY = "Application key"
|
||||||
OVH_APPLICATION_SECRET = "Application secret (Application Key authentication)"
|
OVH_APPLICATION_SECRET = "Application secret"
|
||||||
OVH_CONSUMER_KEY = "Consumer key (Application Key authentication)"
|
OVH_CONSUMER_KEY = "Consumer key"
|
||||||
OVH_CLIENT_ID = "Client ID (OAuth2)"
|
|
||||||
OVH_CLIENT_SECRET = "Client secret (OAuth2)"
|
|
||||||
OVH_ACCESS_TOKEN = "Access token"
|
|
||||||
[Configuration.Additional]
|
[Configuration.Additional]
|
||||||
OVH_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)"
|
OVH_POLLING_INTERVAL = "Time between DNS propagation check"
|
||||||
OVH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 60)"
|
OVH_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
|
||||||
OVH_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)"
|
OVH_TTL = "The TTL of the TXT record used for the DNS challenge"
|
||||||
OVH_HTTP_TIMEOUT = "API request timeout in seconds (Default: 180)"
|
OVH_HTTP_TIMEOUT = "API request timeout"
|
||||||
|
|
||||||
[Links]
|
[Links]
|
||||||
API = "https://eu.api.ovh.com/"
|
API = "https://eu.api.ovh.com/"
|
||||||
|
|
|
||||||
236
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/client.go
generated
vendored
236
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/client.go
generated
vendored
|
|
@ -1,236 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/internal/errutils"
|
|
||||||
"github.com/miekg/dns"
|
|
||||||
)
|
|
||||||
|
|
||||||
// APIKeyHeader API key header.
|
|
||||||
const APIKeyHeader = "X-Api-Key"
|
|
||||||
|
|
||||||
// Client the PowerDNS API client.
|
|
||||||
type Client struct {
|
|
||||||
serverName string
|
|
||||||
apiKey string
|
|
||||||
|
|
||||||
apiVersion int
|
|
||||||
|
|
||||||
Host *url.URL
|
|
||||||
HTTPClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient creates a new Client.
|
|
||||||
func NewClient(host *url.URL, serverName string, apiVersion int, apiKey string) *Client {
|
|
||||||
return &Client{
|
|
||||||
serverName: serverName,
|
|
||||||
apiKey: apiKey,
|
|
||||||
apiVersion: apiVersion,
|
|
||||||
Host: host,
|
|
||||||
HTTPClient: &http.Client{Timeout: 5 * time.Second},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) APIVersion() int {
|
|
||||||
return c.apiVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SetAPIVersion(ctx context.Context) error {
|
|
||||||
var err error
|
|
||||||
|
|
||||||
c.apiVersion, err = c.getAPIVersion(ctx)
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) getAPIVersion(ctx context.Context) (int, error) {
|
|
||||||
endpoint := c.joinPath("/", "api")
|
|
||||||
|
|
||||||
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := c.do(req)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var versions []apiVersion
|
|
||||||
|
|
||||||
err = json.Unmarshal(result, &versions)
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
latestVersion := 0
|
|
||||||
for _, v := range versions {
|
|
||||||
if v.Version > latestVersion {
|
|
||||||
latestVersion = v.Version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return latestVersion, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) GetHostedZone(ctx context.Context, authZone string) (*HostedZone, error) {
|
|
||||||
endpoint := c.joinPath("/", "servers", c.serverName, "zones", dns.Fqdn(authZone))
|
|
||||||
|
|
||||||
req, err := newJSONRequest(ctx, http.MethodGet, endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := c.do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var zone HostedZone
|
|
||||||
|
|
||||||
err = json.Unmarshal(result, &zone)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// convert pre-v1 API result
|
|
||||||
if len(zone.Records) > 0 {
|
|
||||||
zone.RRSets = []RRSet{}
|
|
||||||
for _, record := range zone.Records {
|
|
||||||
set := RRSet{
|
|
||||||
Name: record.Name,
|
|
||||||
Type: record.Type,
|
|
||||||
Records: []Record{record},
|
|
||||||
}
|
|
||||||
zone.RRSets = append(zone.RRSets, set)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &zone, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) UpdateRecords(ctx context.Context, zone *HostedZone, sets RRSets) error {
|
|
||||||
endpoint := c.joinPath("/", "servers", c.serverName, "zones", zone.ID)
|
|
||||||
|
|
||||||
req, err := newJSONRequest(ctx, http.MethodPatch, endpoint, sets)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Notify(ctx context.Context, zone *HostedZone) error {
|
|
||||||
if c.apiVersion < 1 || zone.Kind != "Master" && zone.Kind != "Slave" {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
endpoint := c.joinPath("/", "servers", c.serverName, "zones", zone.ID, "notify")
|
|
||||||
|
|
||||||
req, err := newJSONRequest(ctx, http.MethodPut, endpoint, nil)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = c.do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) joinPath(elem ...string) *url.URL {
|
|
||||||
p := path.Join(elem...)
|
|
||||||
|
|
||||||
if p != "/api" && c.apiVersion > 0 && !strings.HasPrefix(p, "/api/v") {
|
|
||||||
p = path.Join("/api", "v"+strconv.Itoa(c.apiVersion), p)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Host.JoinPath(p)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) do(req *http.Request) (json.RawMessage, error) {
|
|
||||||
req.Header.Set(APIKeyHeader, c.apiKey)
|
|
||||||
|
|
||||||
resp, err := c.HTTPClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errutils.NewHTTPDoError(req, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() { _ = resp.Body.Close() }()
|
|
||||||
|
|
||||||
if resp.StatusCode != http.StatusUnprocessableEntity && (resp.StatusCode < 200 || resp.StatusCode >= 300) {
|
|
||||||
return nil, errutils.NewUnexpectedResponseStatusCodeError(req, resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
var msg json.RawMessage
|
|
||||||
|
|
||||||
err = json.NewDecoder(resp.Body).Decode(&msg)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, io.EOF) {
|
|
||||||
// empty body
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
// other error
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// check for PowerDNS error message
|
|
||||||
if len(msg) > 0 && msg[0] == '{' {
|
|
||||||
var errInfo apiError
|
|
||||||
|
|
||||||
err = json.Unmarshal(msg, &errInfo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errutils.NewUnmarshalError(req, resp.StatusCode, msg, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if errInfo.ShortMsg != "" {
|
|
||||||
return nil, fmt.Errorf("error talking to PDNS API: %w", errInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return msg, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newJSONRequest(ctx context.Context, method string, endpoint *url.URL, payload any) (*http.Request, error) {
|
|
||||||
buf := new(bytes.Buffer)
|
|
||||||
|
|
||||||
if payload != nil {
|
|
||||||
err := json.NewEncoder(buf).Encode(payload)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to create request JSON body: %w", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, strings.TrimSuffix(endpoint.String(), "/"), buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("unable to create request: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
req.Header.Set("Accept", "application/json")
|
|
||||||
|
|
||||||
// PowerDNS doesn't follow HTTP convention about the "Content-Type" header.
|
|
||||||
if method != http.MethodGet && method != http.MethodDelete {
|
|
||||||
req.Header.Set("Content-Type", "application/json")
|
|
||||||
}
|
|
||||||
|
|
||||||
return req, nil
|
|
||||||
}
|
|
||||||
48
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/types.go
generated
vendored
48
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/internal/types.go
generated
vendored
|
|
@ -1,48 +0,0 @@
|
||||||
package internal
|
|
||||||
|
|
||||||
type Record struct {
|
|
||||||
Content string `json:"content"`
|
|
||||||
Disabled bool `json:"disabled"`
|
|
||||||
|
|
||||||
// pre-v1 API
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
TTL int `json:"ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type HostedZone struct {
|
|
||||||
ID string `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
URL string `json:"url"`
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
RRSets []RRSet `json:"rrsets"`
|
|
||||||
|
|
||||||
// pre-v1 API
|
|
||||||
Records []Record `json:"records"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RRSet struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
ChangeType string `json:"changetype"`
|
|
||||||
Records []Record `json:"records,omitempty"`
|
|
||||||
TTL int `json:"ttl,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RRSets struct {
|
|
||||||
RRSets []RRSet `json:"rrsets"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type apiError struct {
|
|
||||||
ShortMsg string `json:"error"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a apiError) Error() string {
|
|
||||||
return a.ShortMsg
|
|
||||||
}
|
|
||||||
|
|
||||||
type apiVersion struct {
|
|
||||||
URL string `json:"url"`
|
|
||||||
Version int `json:"version"`
|
|
||||||
}
|
|
||||||
257
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.go
generated
vendored
257
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.go
generated
vendored
|
|
@ -1,257 +0,0 @@
|
||||||
// Package pdns implements a DNS provider for solving the DNS-01 challenge using PowerDNS nameserver.
|
|
||||||
package pdns
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
|
||||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
|
||||||
"github.com/go-acme/lego/v4/log"
|
|
||||||
"github.com/go-acme/lego/v4/platform/config/env"
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/internal/clientdebug"
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/pdns/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Environment variables names.
|
|
||||||
const (
|
|
||||||
envNamespace = "PDNS_"
|
|
||||||
|
|
||||||
EnvAPIKey = envNamespace + "API_KEY"
|
|
||||||
EnvAPIURL = envNamespace + "API_URL"
|
|
||||||
|
|
||||||
EnvTTL = envNamespace + "TTL"
|
|
||||||
EnvAPIVersion = envNamespace + "API_VERSION"
|
|
||||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
|
||||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
|
||||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
|
||||||
EnvServerName = envNamespace + "SERVER_NAME"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
|
||||||
|
|
||||||
// Config is used to configure the creation of the DNSProvider.
|
|
||||||
type Config struct {
|
|
||||||
APIKey string
|
|
||||||
Host *url.URL
|
|
||||||
ServerName string
|
|
||||||
APIVersion int
|
|
||||||
PropagationTimeout time.Duration
|
|
||||||
PollingInterval time.Duration
|
|
||||||
TTL int
|
|
||||||
HTTPClient *http.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDefaultConfig returns a default configuration for the DNSProvider.
|
|
||||||
func NewDefaultConfig() *Config {
|
|
||||||
return &Config{
|
|
||||||
ServerName: env.GetOrDefaultString(EnvServerName, "localhost"),
|
|
||||||
APIVersion: env.GetOrDefaultInt(EnvAPIVersion, 0),
|
|
||||||
TTL: env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
|
|
||||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second),
|
|
||||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second),
|
|
||||||
HTTPClient: &http.Client{
|
|
||||||
Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// DNSProvider implements the challenge.Provider interface.
|
|
||||||
type DNSProvider struct {
|
|
||||||
config *Config
|
|
||||||
client *internal.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDNSProvider returns a DNSProvider instance configured for pdns.
|
|
||||||
// Credentials must be passed in the environment variable:
|
|
||||||
// PDNS_API_URL and PDNS_API_KEY.
|
|
||||||
func NewDNSProvider() (*DNSProvider, error) {
|
|
||||||
values, err := env.Get(EnvAPIKey, EnvAPIURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("pdns: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
hostURL, err := url.Parse(values[EnvAPIURL])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("pdns: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
config := NewDefaultConfig()
|
|
||||||
config.Host = hostURL
|
|
||||||
config.APIKey = values[EnvAPIKey]
|
|
||||||
|
|
||||||
return NewDNSProviderConfig(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDNSProviderConfig return a DNSProvider instance configured for pdns.
|
|
||||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
|
||||||
if config == nil {
|
|
||||||
return nil, errors.New("pdns: the configuration of the DNS provider is nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.APIKey == "" {
|
|
||||||
return nil, errors.New("pdns: API key missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Host == nil || config.Host.Host == "" {
|
|
||||||
return nil, errors.New("pdns: API URL missing")
|
|
||||||
}
|
|
||||||
|
|
||||||
client := internal.NewClient(config.Host, config.ServerName, config.APIVersion, config.APIKey)
|
|
||||||
|
|
||||||
if config.HTTPClient != nil {
|
|
||||||
client.HTTPClient = config.HTTPClient
|
|
||||||
}
|
|
||||||
|
|
||||||
client.HTTPClient = clientdebug.Wrap(client.HTTPClient)
|
|
||||||
|
|
||||||
if config.APIVersion <= 0 {
|
|
||||||
err := client.SetAPIVersion(context.Background())
|
|
||||||
if err != nil {
|
|
||||||
log.Warnf("pdns: failed to get API version %v", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &DNSProvider{config: config, client: client}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Timeout returns the timeout and interval to use when checking for DNS propagation.
|
|
||||||
// Adjusting here to cope with spikes in propagation times.
|
|
||||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
|
||||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
|
||||||
}
|
|
||||||
|
|
||||||
// Present creates a TXT record to fulfill the dns-01 challenge.
|
|
||||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
|
||||||
|
|
||||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: could not find zone for domain %q: %w", domain, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := d.client.GetHostedZone(ctx, authZone)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: get hosted zone for %s: %w", authZone, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
name := info.EffectiveFQDN
|
|
||||||
if d.client.APIVersion() == 0 {
|
|
||||||
// pre-v1 API wants non-fqdn
|
|
||||||
name = dns01.UnFqdn(info.EffectiveFQDN)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for existing records.
|
|
||||||
existingRRSet := findTxtRecord(zone, info.EffectiveFQDN)
|
|
||||||
|
|
||||||
var records []internal.Record
|
|
||||||
if existingRRSet != nil {
|
|
||||||
records = existingRRSet.Records
|
|
||||||
}
|
|
||||||
|
|
||||||
records = append(records, internal.Record{
|
|
||||||
Content: strconv.Quote(info.Value),
|
|
||||||
Disabled: false,
|
|
||||||
|
|
||||||
// pre-v1 API
|
|
||||||
Type: "TXT",
|
|
||||||
Name: name,
|
|
||||||
TTL: d.config.TTL,
|
|
||||||
})
|
|
||||||
|
|
||||||
rrSets := internal.RRSets{
|
|
||||||
RRSets: []internal.RRSet{{
|
|
||||||
Name: name,
|
|
||||||
ChangeType: "REPLACE",
|
|
||||||
Type: "TXT",
|
|
||||||
Kind: "Master",
|
|
||||||
TTL: d.config.TTL,
|
|
||||||
Records: records,
|
|
||||||
}},
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.client.UpdateRecords(ctx, zone, rrSets)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: update records: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.client.Notify(ctx, zone)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: notify: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// CleanUp removes the TXT record matching the specified parameters.
|
|
||||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
|
|
||||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
|
||||||
|
|
||||||
authZone, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: could not find zone for domain %q: %w", domain, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
zone, err := d.client.GetHostedZone(ctx, authZone)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: get hosted zone for %s: %w", authZone, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Look for existing records.
|
|
||||||
set := findTxtRecord(zone, info.EffectiveFQDN)
|
|
||||||
if set == nil {
|
|
||||||
return fmt.Errorf("pdns: no existing record found for %s", info.EffectiveFQDN)
|
|
||||||
}
|
|
||||||
|
|
||||||
var records []internal.Record
|
|
||||||
|
|
||||||
for _, r := range set.Records {
|
|
||||||
if r.Content != strconv.Quote(info.Value) {
|
|
||||||
records = append(records, r)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rrSet := internal.RRSet{
|
|
||||||
Name: set.Name,
|
|
||||||
Type: set.Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(records) > 0 {
|
|
||||||
rrSet.ChangeType = "REPLACE"
|
|
||||||
rrSet.TTL = d.config.TTL
|
|
||||||
rrSet.Records = records
|
|
||||||
} else {
|
|
||||||
rrSet.ChangeType = "DELETE"
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.client.UpdateRecords(ctx, zone, internal.RRSets{RRSets: []internal.RRSet{rrSet}})
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: update records: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = d.client.Notify(ctx, zone)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("pdns: notify: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findTxtRecord(zone *internal.HostedZone, fqdn string) *internal.RRSet {
|
|
||||||
for _, set := range zone.RRSets {
|
|
||||||
if set.Type == "TXT" && (set.Name == dns01.UnFqdn(fqdn) || set.Name == fqdn) {
|
|
||||||
return &set
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
37
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.toml
generated
vendored
37
vendor/github.com/go-acme/lego/v4/providers/dns/pdns/pdns.toml
generated
vendored
|
|
@ -1,37 +0,0 @@
|
||||||
Name = "PowerDNS"
|
|
||||||
Description = ''''''
|
|
||||||
URL = "https://www.powerdns.com/"
|
|
||||||
Code = "pdns"
|
|
||||||
Since = "v0.4.0"
|
|
||||||
|
|
||||||
Example = '''
|
|
||||||
PDNS_API_URL=http://pdns-server:80/ \
|
|
||||||
PDNS_API_KEY=xxxx \
|
|
||||||
lego --email you@example.com --dns pdns -d '*.example.com' -d example.com run
|
|
||||||
'''
|
|
||||||
|
|
||||||
Additional = '''
|
|
||||||
## Information
|
|
||||||
|
|
||||||
Tested and confirmed to work with PowerDNS authoritative server 3.4.8 and 4.0.1. Refer to [PowerDNS documentation](https://doc.powerdns.com/md/httpapi/README/) instructions on how to enable the built-in API interface.
|
|
||||||
|
|
||||||
PowerDNS Notes:
|
|
||||||
- PowerDNS API does not currently support SSL, therefore you should take care to ensure that traffic between lego and the PowerDNS API is over a trusted network, VPN etc.
|
|
||||||
- In order to have the SOA serial automatically increment each time the `_acme-challenge` record is added/modified via the API, set `SOA-EDIT-API` to `INCEPTION-INCREMENT` for the zone in the `domainmetadata` table
|
|
||||||
- Some PowerDNS servers doesn't have root API endpoints enabled and API version autodetection will not work. In that case version number can be defined using `PDNS_API_VERSION`.
|
|
||||||
'''
|
|
||||||
|
|
||||||
[Configuration]
|
|
||||||
[Configuration.Credentials]
|
|
||||||
PDNS_API_KEY = "API key"
|
|
||||||
PDNS_API_URL = "API URL"
|
|
||||||
[Configuration.Additional]
|
|
||||||
PDNS_SERVER_NAME = "Name of the server in the URL, 'localhost' by default"
|
|
||||||
PDNS_API_VERSION = "Skip API version autodetection and use the provided version number."
|
|
||||||
PDNS_POLLING_INTERVAL = "Time between DNS propagation check in seconds (Default: 2)"
|
|
||||||
PDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation in seconds (Default: 120)"
|
|
||||||
PDNS_TTL = "The TTL of the TXT record used for the DNS challenge in seconds (Default: 120)"
|
|
||||||
PDNS_HTTP_TIMEOUT = "API request timeout in seconds (Default: 30)"
|
|
||||||
|
|
||||||
[Links]
|
|
||||||
API = "https://doc.powerdns.com/md/httpapi/README/"
|
|
||||||
17
vendor/github.com/go-acme/lego/v4/registration/registar.go
generated
vendored
17
vendor/github.com/go-acme/lego/v4/registration/registar.go
generated
vendored
|
|
@ -9,13 +9,11 @@ import (
|
||||||
"github.com/go-acme/lego/v4/log"
|
"github.com/go-acme/lego/v4/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const mailTo = "mailto:"
|
|
||||||
|
|
||||||
// Resource represents all important information about a registration
|
// Resource represents all important information about a registration
|
||||||
// of which the client needs to keep track itself.
|
// of which the client needs to keep track itself.
|
||||||
// WARNING: will be removed in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
|
// WARNING: will be remove in the future (acme.ExtendedAccount), https://github.com/go-acme/lego/issues/855.
|
||||||
type Resource struct {
|
type Resource struct {
|
||||||
Body acme.Account `json:"body"`
|
Body acme.Account `json:"body,omitempty"`
|
||||||
URI string `json:"uri,omitempty"`
|
URI string `json:"uri,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -54,13 +52,13 @@ func (r *Registrar) Register(options RegisterOptions) (*Resource, error) {
|
||||||
|
|
||||||
if r.user.GetEmail() != "" {
|
if r.user.GetEmail() != "" {
|
||||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
accMsg.Contact = []string{mailTo + r.user.GetEmail()}
|
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := r.core.Accounts.New(accMsg)
|
account, err := r.core.Accounts.New(accMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// seems impossible
|
// seems impossible
|
||||||
errorDetails := &acme.ProblemDetails{}
|
var errorDetails acme.ProblemDetails
|
||||||
if !errors.As(err, &errorDetails) || errorDetails.HTTPStatus != http.StatusConflict {
|
if !errors.As(err, &errorDetails) || errorDetails.HTTPStatus != http.StatusConflict {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -78,13 +76,13 @@ func (r *Registrar) RegisterWithExternalAccountBinding(options RegisterEABOption
|
||||||
|
|
||||||
if r.user.GetEmail() != "" {
|
if r.user.GetEmail() != "" {
|
||||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
accMsg.Contact = []string{mailTo + r.user.GetEmail()}
|
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||||
}
|
}
|
||||||
|
|
||||||
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
|
account, err := r.core.Accounts.NewEAB(accMsg, options.Kid, options.HmacEncoded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// seems impossible
|
// seems impossible
|
||||||
errorDetails := &acme.ProblemDetails{}
|
var errorDetails acme.ProblemDetails
|
||||||
if !errors.As(err, &errorDetails) || errorDetails.HTTPStatus != http.StatusConflict {
|
if !errors.As(err, &errorDetails) || errorDetails.HTTPStatus != http.StatusConflict {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -130,7 +128,7 @@ func (r *Registrar) UpdateRegistration(options RegisterOptions) (*Resource, erro
|
||||||
|
|
||||||
if r.user.GetEmail() != "" {
|
if r.user.GetEmail() != "" {
|
||||||
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
log.Infof("acme: Registering account for %s", r.user.GetEmail())
|
||||||
accMsg.Contact = []string{mailTo + r.user.GetEmail()}
|
accMsg.Contact = []string{"mailto:" + r.user.GetEmail()}
|
||||||
}
|
}
|
||||||
|
|
||||||
accountURL := r.user.GetRegistration().URI
|
accountURL := r.user.GetRegistration().URI
|
||||||
|
|
@ -160,7 +158,6 @@ func (r *Registrar) ResolveAccountByKey() (*Resource, error) {
|
||||||
log.Infof("acme: Trying to resolve account by key")
|
log.Infof("acme: Trying to resolve account by key")
|
||||||
|
|
||||||
accMsg := acme.Account{OnlyReturnExisting: true}
|
accMsg := acme.Account{OnlyReturnExisting: true}
|
||||||
|
|
||||||
account, err := r.core.Accounts.New(accMsg)
|
account, err := r.core.Accounts.New(accMsg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
53
vendor/github.com/go-jose/go-jose/v4/.golangci.yml
generated
vendored
53
vendor/github.com/go-jose/go-jose/v4/.golangci.yml
generated
vendored
|
|
@ -1,53 +0,0 @@
|
||||||
# https://github.com/golangci/golangci-lint
|
|
||||||
|
|
||||||
run:
|
|
||||||
skip-files:
|
|
||||||
- doc_test.go
|
|
||||||
modules-download-mode: readonly
|
|
||||||
|
|
||||||
linters:
|
|
||||||
enable-all: true
|
|
||||||
disable:
|
|
||||||
- gochecknoglobals
|
|
||||||
- goconst
|
|
||||||
- lll
|
|
||||||
- maligned
|
|
||||||
- nakedret
|
|
||||||
- scopelint
|
|
||||||
- unparam
|
|
||||||
- funlen # added in 1.18 (requires go-jose changes before it can be enabled)
|
|
||||||
|
|
||||||
linters-settings:
|
|
||||||
gocyclo:
|
|
||||||
min-complexity: 35
|
|
||||||
|
|
||||||
issues:
|
|
||||||
exclude-rules:
|
|
||||||
- text: "don't use ALL_CAPS in Go names"
|
|
||||||
linters:
|
|
||||||
- golint
|
|
||||||
- text: "hardcoded credentials"
|
|
||||||
linters:
|
|
||||||
- gosec
|
|
||||||
- text: "weak cryptographic primitive"
|
|
||||||
linters:
|
|
||||||
- gosec
|
|
||||||
- path: json/
|
|
||||||
linters:
|
|
||||||
- dupl
|
|
||||||
- errcheck
|
|
||||||
- gocritic
|
|
||||||
- gocyclo
|
|
||||||
- golint
|
|
||||||
- govet
|
|
||||||
- ineffassign
|
|
||||||
- staticcheck
|
|
||||||
- structcheck
|
|
||||||
- stylecheck
|
|
||||||
- unused
|
|
||||||
- path: _test\.go
|
|
||||||
linters:
|
|
||||||
- scopelint
|
|
||||||
- path: jwk.go
|
|
||||||
linters:
|
|
||||||
- gocyclo
|
|
||||||
33
vendor/github.com/go-jose/go-jose/v4/.travis.yml
generated
vendored
33
vendor/github.com/go-jose/go-jose/v4/.travis.yml
generated
vendored
|
|
@ -1,33 +0,0 @@
|
||||||
language: go
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
fast_finish: true
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
go:
|
|
||||||
- "1.13.x"
|
|
||||||
- "1.14.x"
|
|
||||||
- tip
|
|
||||||
|
|
||||||
before_script:
|
|
||||||
- export PATH=$HOME/.local/bin:$PATH
|
|
||||||
|
|
||||||
before_install:
|
|
||||||
- go get -u github.com/mattn/goveralls github.com/wadey/gocovmerge
|
|
||||||
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s -- -b $(go env GOPATH)/bin v1.18.0
|
|
||||||
- pip install cram --user
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test -v -covermode=count -coverprofile=profile.cov .
|
|
||||||
- go test -v -covermode=count -coverprofile=cryptosigner/profile.cov ./cryptosigner
|
|
||||||
- go test -v -covermode=count -coverprofile=cipher/profile.cov ./cipher
|
|
||||||
- go test -v -covermode=count -coverprofile=jwt/profile.cov ./jwt
|
|
||||||
- go test -v ./json # no coverage for forked encoding/json package
|
|
||||||
- golangci-lint run
|
|
||||||
- cd jose-util && go build && PATH=$PWD:$PATH cram -v jose-util.t # cram tests jose-util
|
|
||||||
- cd ..
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- gocovmerge *.cov */*.cov > merged.coverprofile
|
|
||||||
- goveralls -coverprofile merged.coverprofile -service=travis-ci
|
|
||||||
108
vendor/github.com/go-jose/go-jose/v4/README.md
generated
vendored
108
vendor/github.com/go-jose/go-jose/v4/README.md
generated
vendored
|
|
@ -1,108 +0,0 @@
|
||||||
# Go JOSE
|
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
|
|
||||||
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
|
|
||||||
[](https://raw.githubusercontent.com/go-jose/go-jose/master/LICENSE)
|
|
||||||
|
|
||||||
Package jose aims to provide an implementation of the Javascript Object Signing
|
|
||||||
and Encryption set of standards. This includes support for JSON Web Encryption,
|
|
||||||
JSON Web Signature, and JSON Web Token standards.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The implementation follows the
|
|
||||||
[JSON Web Encryption](https://dx.doi.org/10.17487/RFC7516) (RFC 7516),
|
|
||||||
[JSON Web Signature](https://dx.doi.org/10.17487/RFC7515) (RFC 7515), and
|
|
||||||
[JSON Web Token](https://dx.doi.org/10.17487/RFC7519) (RFC 7519) specifications.
|
|
||||||
Tables of supported algorithms are shown below. The library supports both
|
|
||||||
the compact and JWS/JWE JSON Serialization formats, and has optional support for
|
|
||||||
multiple recipients. It also comes with a small command-line utility
|
|
||||||
([`jose-util`](https://pkg.go.dev/github.com/go-jose/go-jose/jose-util))
|
|
||||||
for dealing with JOSE messages in a shell.
|
|
||||||
|
|
||||||
**Note**: We use a forked version of the `encoding/json` package from the Go
|
|
||||||
standard library which uses case-sensitive matching for member names (instead
|
|
||||||
of [case-insensitive matching](https://www.ietf.org/mail-archive/web/json/current/msg03763.html)).
|
|
||||||
This is to avoid differences in interpretation of messages between go-jose and
|
|
||||||
libraries in other languages.
|
|
||||||
|
|
||||||
### Versions
|
|
||||||
|
|
||||||
The forthcoming Version 5 will be released with several breaking API changes,
|
|
||||||
and will require Golang's `encoding/json/v2`, which is currently requires
|
|
||||||
Go 1.25 built with GOEXPERIMENT=jsonv2.
|
|
||||||
|
|
||||||
Version 4 is the current stable version:
|
|
||||||
|
|
||||||
import "github.com/go-jose/go-jose/v4"
|
|
||||||
|
|
||||||
It supports at least the current and previous Golang release. Currently it
|
|
||||||
requires Golang 1.24.
|
|
||||||
|
|
||||||
Version 3 is only receiving critical security updates. Migration to Version 4 is recommended.
|
|
||||||
|
|
||||||
Versions 1 and 2 are obsolete, but can be found in the old repository, [square/go-jose](https://github.com/square/go-jose).
|
|
||||||
|
|
||||||
### Supported algorithms
|
|
||||||
|
|
||||||
See below for a table of supported algorithms. Algorithm identifiers match
|
|
||||||
the names in the [JSON Web Algorithms](https://dx.doi.org/10.17487/RFC7518)
|
|
||||||
standard where possible. The Godoc reference has a list of constants.
|
|
||||||
|
|
||||||
| Key encryption | Algorithm identifier(s) |
|
|
||||||
|:-----------------------|:-----------------------------------------------|
|
|
||||||
| RSA-PKCS#1v1.5 | RSA1_5 |
|
|
||||||
| RSA-OAEP | RSA-OAEP, RSA-OAEP-256 |
|
|
||||||
| AES key wrap | A128KW, A192KW, A256KW |
|
|
||||||
| AES-GCM key wrap | A128GCMKW, A192GCMKW, A256GCMKW |
|
|
||||||
| ECDH-ES + AES key wrap | ECDH-ES+A128KW, ECDH-ES+A192KW, ECDH-ES+A256KW |
|
|
||||||
| ECDH-ES (direct) | ECDH-ES<sup>1</sup> |
|
|
||||||
| Direct encryption | dir<sup>1</sup> |
|
|
||||||
|
|
||||||
<sup>1. Not supported in multi-recipient mode</sup>
|
|
||||||
|
|
||||||
| Signing / MAC | Algorithm identifier(s) |
|
|
||||||
|:------------------|:------------------------|
|
|
||||||
| RSASSA-PKCS#1v1.5 | RS256, RS384, RS512 |
|
|
||||||
| RSASSA-PSS | PS256, PS384, PS512 |
|
|
||||||
| HMAC | HS256, HS384, HS512 |
|
|
||||||
| ECDSA | ES256, ES384, ES512 |
|
|
||||||
| Ed25519 | EdDSA<sup>2</sup> |
|
|
||||||
|
|
||||||
<sup>2. Only available in version 2 of the package</sup>
|
|
||||||
|
|
||||||
| Content encryption | Algorithm identifier(s) |
|
|
||||||
|:-------------------|:--------------------------------------------|
|
|
||||||
| AES-CBC+HMAC | A128CBC-HS256, A192CBC-HS384, A256CBC-HS512 |
|
|
||||||
| AES-GCM | A128GCM, A192GCM, A256GCM |
|
|
||||||
|
|
||||||
| Compression | Algorithm identifiers(s) |
|
|
||||||
|:-------------------|--------------------------|
|
|
||||||
| DEFLATE (RFC 1951) | DEF |
|
|
||||||
|
|
||||||
### Supported key types
|
|
||||||
|
|
||||||
See below for a table of supported key types. These are understood by the
|
|
||||||
library, and can be passed to corresponding functions such as `NewEncrypter` or
|
|
||||||
`NewSigner`. Each of these keys can also be wrapped in a JWK if desired, which
|
|
||||||
allows attaching a key id.
|
|
||||||
|
|
||||||
| Algorithm(s) | Corresponding types |
|
|
||||||
|:------------------|--------------------------------------------------------------------------------------------------------------------------------------|
|
|
||||||
| RSA | *[rsa.PublicKey](https://pkg.go.dev/crypto/rsa/#PublicKey), *[rsa.PrivateKey](https://pkg.go.dev/crypto/rsa/#PrivateKey) |
|
|
||||||
| ECDH, ECDSA | *[ecdsa.PublicKey](https://pkg.go.dev/crypto/ecdsa/#PublicKey), *[ecdsa.PrivateKey](https://pkg.go.dev/crypto/ecdsa/#PrivateKey) |
|
|
||||||
| EdDSA<sup>1</sup> | [ed25519.PublicKey](https://pkg.go.dev/crypto/ed25519#PublicKey), [ed25519.PrivateKey](https://pkg.go.dev/crypto/ed25519#PrivateKey) |
|
|
||||||
| AES, HMAC | []byte |
|
|
||||||
|
|
||||||
<sup>1. Only available in version 2 or later of the package</sup>
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4)
|
|
||||||
[](https://pkg.go.dev/github.com/go-jose/go-jose/v4/jwt)
|
|
||||||
|
|
||||||
Examples can be found in the Godoc
|
|
||||||
reference for this package. The
|
|
||||||
[`jose-util`](https://github.com/go-jose/go-jose/tree/main/jose-util)
|
|
||||||
subdirectory also contains a small command-line utility which might be useful
|
|
||||||
as an example as well.
|
|
||||||
13
vendor/github.com/go-jose/go-jose/v4/SECURITY.md
generated
vendored
13
vendor/github.com/go-jose/go-jose/v4/SECURITY.md
generated
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
# Security Policy
|
|
||||||
This document explains how to contact the Let's Encrypt security team to report security vulnerabilities.
|
|
||||||
|
|
||||||
## Supported Versions
|
|
||||||
| Version | Supported |
|
|
||||||
| ------- | ----------|
|
|
||||||
| >= v3 | ✓ |
|
|
||||||
| v2 | ✗ |
|
|
||||||
| v1 | ✗ |
|
|
||||||
|
|
||||||
## Reporting a vulnerability
|
|
||||||
|
|
||||||
Please see [https://letsencrypt.org/contact/#security](https://letsencrypt.org/contact/#security) for the email address to report a vulnerability. Ensure that the subject line for your report contains the word `vulnerability` and is descriptive. Your email should be acknowledged within 24 hours. If you do not receive a response within 24 hours, please follow-up again with another email.
|
|
||||||
11
vendor/github.com/goccy/go-json/.golangci.yml
generated
vendored
11
vendor/github.com/goccy/go-json/.golangci.yml
generated
vendored
|
|
@ -48,17 +48,6 @@ linters:
|
||||||
- nlreturn
|
- nlreturn
|
||||||
- testpackage
|
- testpackage
|
||||||
- wsl
|
- wsl
|
||||||
- varnamelen
|
|
||||||
- nilnil
|
|
||||||
- ireturn
|
|
||||||
- govet
|
|
||||||
- forcetypeassert
|
|
||||||
- cyclop
|
|
||||||
- containedctx
|
|
||||||
- revive
|
|
||||||
- nosnakecase
|
|
||||||
- exhaustruct
|
|
||||||
- depguard
|
|
||||||
|
|
||||||
issues:
|
issues:
|
||||||
exclude-rules:
|
exclude-rules:
|
||||||
|
|
|
||||||
165
vendor/github.com/goccy/go-json/CHANGELOG.md
generated
vendored
165
vendor/github.com/goccy/go-json/CHANGELOG.md
generated
vendored
|
|
@ -1,168 +1,3 @@
|
||||||
# v0.10.2 - 2023/03/20
|
|
||||||
|
|
||||||
### New features
|
|
||||||
|
|
||||||
* Support DebugDOT option for debugging encoder ( #440 )
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix combination of embedding structure and omitempty option ( #442 )
|
|
||||||
|
|
||||||
# v0.10.1 - 2023/03/13
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix checkptr error for array decoder ( #415 )
|
|
||||||
* Fix added buffer size check when decoding key ( #430 )
|
|
||||||
* Fix handling of anonymous fields other than struct ( #431 )
|
|
||||||
* Fix to not optimize when lower conversion can't handle byte-by-byte ( #432 )
|
|
||||||
* Fix a problem that MarshalIndent does not work when UnorderedMap is specified ( #435 )
|
|
||||||
* Fix mapDecoder.DecodeStream() for empty objects containing whitespace ( #425 )
|
|
||||||
* Fix an issue that could not set the correct NextField for fields in the embedded structure ( #438 )
|
|
||||||
|
|
||||||
# v0.10.0 - 2022/11/29
|
|
||||||
|
|
||||||
### New features
|
|
||||||
|
|
||||||
* Support JSON Path ( #250 )
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix marshaler for map's key ( #409 )
|
|
||||||
|
|
||||||
# v0.9.11 - 2022/08/18
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix unexpected behavior when buffer ends with backslash ( #383 )
|
|
||||||
* Fix stream decoding of escaped character ( #387 )
|
|
||||||
|
|
||||||
# v0.9.10 - 2022/07/15
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix boundary exception of type caching ( #382 )
|
|
||||||
|
|
||||||
# v0.9.9 - 2022/07/15
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix encoding of directed interface with typed nil ( #377 )
|
|
||||||
* Fix embedded primitive type encoding using alias ( #378 )
|
|
||||||
* Fix slice/array type encoding with types implementing MarshalJSON ( #379 )
|
|
||||||
* Fix unicode decoding when the expected buffer state is not met after reading ( #380 )
|
|
||||||
|
|
||||||
# v0.9.8 - 2022/06/30
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix decoding of surrogate-pair ( #365 )
|
|
||||||
* Fix handling of embedded primitive type ( #366 )
|
|
||||||
* Add validation of escape sequence for decoder ( #367 )
|
|
||||||
* Fix stream tokenizing respecting UseNumber ( #369 )
|
|
||||||
* Fix encoding when struct pointer type that implements Marshal JSON is embedded ( #375 )
|
|
||||||
|
|
||||||
### Improve performance
|
|
||||||
|
|
||||||
* Improve performance of linkRecursiveCode ( #368 )
|
|
||||||
|
|
||||||
# v0.9.7 - 2022/04/22
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
#### Encoder
|
|
||||||
|
|
||||||
* Add filtering process for encoding on slow path ( #355 )
|
|
||||||
* Fix encoding of interface{} with pointer type ( #363 )
|
|
||||||
|
|
||||||
#### Decoder
|
|
||||||
|
|
||||||
* Fix map key decoder that implements UnmarshalJSON ( #353 )
|
|
||||||
* Fix decoding of []uint8 type ( #361 )
|
|
||||||
|
|
||||||
### New features
|
|
||||||
|
|
||||||
* Add DebugWith option for encoder ( #356 )
|
|
||||||
|
|
||||||
# v0.9.6 - 2022/03/22
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Correct the handling of the minimum value of int type for decoder ( #344 )
|
|
||||||
* Fix bugs of stream decoder's bufferSize ( #349 )
|
|
||||||
* Add a guard to use typeptr more safely ( #351 )
|
|
||||||
|
|
||||||
### Improve decoder performance
|
|
||||||
|
|
||||||
* Improve escapeString's performance ( #345 )
|
|
||||||
|
|
||||||
### Others
|
|
||||||
|
|
||||||
* Update go version for CI ( #347 )
|
|
||||||
|
|
||||||
# v0.9.5 - 2022/03/04
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix panic when decoding time.Time with context ( #328 )
|
|
||||||
* Fix reading the next character in buffer to nul consideration ( #338 )
|
|
||||||
* Fix incorrect handling on skipValue ( #341 )
|
|
||||||
|
|
||||||
### Improve decoder performance
|
|
||||||
|
|
||||||
* Improve performance when a payload contains escape sequence ( #334 )
|
|
||||||
|
|
||||||
# v0.9.4 - 2022/01/21
|
|
||||||
|
|
||||||
* Fix IsNilForMarshaler for string type with omitempty ( #323 )
|
|
||||||
* Fix the case where the embedded field is at the end ( #326 )
|
|
||||||
|
|
||||||
# v0.9.3 - 2022/01/14
|
|
||||||
|
|
||||||
* Fix logic of removing struct field for decoder ( #322 )
|
|
||||||
|
|
||||||
# v0.9.2 - 2022/01/14
|
|
||||||
|
|
||||||
* Add invalid decoder to delay type error judgment at decode ( #321 )
|
|
||||||
|
|
||||||
# v0.9.1 - 2022/01/11
|
|
||||||
|
|
||||||
* Fix encoding of MarshalText/MarshalJSON operation with head offset ( #319 )
|
|
||||||
|
|
||||||
# v0.9.0 - 2022/01/05
|
|
||||||
|
|
||||||
### New feature
|
|
||||||
|
|
||||||
* Supports dynamic filtering of struct fields ( #314 )
|
|
||||||
|
|
||||||
### Improve encoding performance
|
|
||||||
|
|
||||||
* Improve map encoding performance ( #310 )
|
|
||||||
* Optimize encoding path for escaped string ( #311 )
|
|
||||||
* Add encoding option for performance ( #312 )
|
|
||||||
|
|
||||||
### Fix bugs
|
|
||||||
|
|
||||||
* Fix panic at encoding map value on 1.18 ( #310 )
|
|
||||||
* Fix MarshalIndent for interface type ( #317 )
|
|
||||||
|
|
||||||
# v0.8.1 - 2021/12/05
|
|
||||||
|
|
||||||
* Fix operation conversion from PtrHead to Head in Recursive type ( #305 )
|
|
||||||
|
|
||||||
# v0.8.0 - 2021/12/02
|
|
||||||
|
|
||||||
* Fix embedded field conflict behavior ( #300 )
|
|
||||||
* Refactor compiler for encoder ( #301 #302 )
|
|
||||||
|
|
||||||
# v0.7.10 - 2021/10/16
|
|
||||||
|
|
||||||
* Fix conversion from pointer to uint64 ( #294 )
|
|
||||||
|
|
||||||
# v0.7.9 - 2021/09/28
|
|
||||||
|
|
||||||
* Fix encoding of nil value about interface type that has method ( #291 )
|
|
||||||
|
|
||||||
# v0.7.8 - 2021/09/01
|
# v0.7.8 - 2021/09/01
|
||||||
|
|
||||||
* Fix mapassign_faststr for indirect struct type ( #283 )
|
* Fix mapassign_faststr for indirect struct type ( #283 )
|
||||||
|
|
|
||||||
4
vendor/github.com/goccy/go-json/Makefile
generated
vendored
4
vendor/github.com/goccy/go-json/Makefile
generated
vendored
|
|
@ -22,7 +22,7 @@ cover-html: cover
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: golangci-lint
|
lint: golangci-lint
|
||||||
$(BIN_DIR)/golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
golangci-lint: | $(BIN_DIR)
|
golangci-lint: | $(BIN_DIR)
|
||||||
@{ \
|
@{ \
|
||||||
|
|
@ -30,7 +30,7 @@ golangci-lint: | $(BIN_DIR)
|
||||||
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
|
GOLANGCI_LINT_TMP_DIR=$$(mktemp -d); \
|
||||||
cd $$GOLANGCI_LINT_TMP_DIR; \
|
cd $$GOLANGCI_LINT_TMP_DIR; \
|
||||||
go mod init tmp; \
|
go mod init tmp; \
|
||||||
GOBIN=$(BIN_DIR) go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.54.2; \
|
GOBIN=$(BIN_DIR) go get github.com/golangci/golangci-lint/cmd/golangci-lint@v1.36.0; \
|
||||||
rm -rf $$GOLANGCI_LINT_TMP_DIR; \
|
rm -rf $$GOLANGCI_LINT_TMP_DIR; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
8
vendor/github.com/goccy/go-json/README.md
generated
vendored
8
vendor/github.com/goccy/go-json/README.md
generated
vendored
|
|
@ -13,7 +13,7 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
|
||||||
```
|
```
|
||||||
* version ( expected release date )
|
* version ( expected release date )
|
||||||
|
|
||||||
* v0.9.0
|
* v0.7.0
|
||||||
|
|
|
|
||||||
| while maintaining compatibility with encoding/json, we will add convenient APIs
|
| while maintaining compatibility with encoding/json, we will add convenient APIs
|
||||||
|
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ Fast JSON encoder/decoder compatible with encoding/json for Go
|
||||||
* v1.0.0
|
* v1.0.0
|
||||||
```
|
```
|
||||||
|
|
||||||
We are accepting requests for features that will be implemented between v0.9.0 and v.1.0.0.
|
We are accepting requests for features that will be implemented between v0.7.0 and v.1.0.0.
|
||||||
If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues).
|
If you have the API you need, please submit your issue [here](https://github.com/goccy/go-json/issues).
|
||||||
|
For example, I'm thinking of supporting `context.Context` of `json.Marshaler` and decoding using JSON Path.
|
||||||
|
|
||||||
# Features
|
# Features
|
||||||
|
|
||||||
|
|
@ -31,7 +32,6 @@ If you have the API you need, please submit your issue [here](https://github.com
|
||||||
- Flexible customization with options
|
- Flexible customization with options
|
||||||
- Coloring the encoded string
|
- Coloring the encoded string
|
||||||
- Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
|
- Can propagate context.Context to `MarshalJSON` or `UnmarshalJSON`
|
||||||
- Can dynamically filter the fields of the structure type-safely
|
|
||||||
|
|
||||||
# Installation
|
# Installation
|
||||||
|
|
||||||
|
|
@ -184,7 +184,7 @@ func Marshal(v interface{}) ([]byte, error) {
|
||||||
`json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
|
`json.Marshal` and `json.Unmarshal` receive `interface{}` value and they perform type determination dynamically to process.
|
||||||
In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
|
In normal case, you need to use the `reflect` library to determine the type dynamically, but since `reflect.Type` is defined as `interface`, when you call the method of `reflect.Type`, The reflect's argument is escaped.
|
||||||
|
|
||||||
Therefore, the arguments for `Marshal` and `Unmarshal` are always escaped to the heap.
|
Therefore, the arguments for `Marshal` and `Unmarshal` are always escape to the heap.
|
||||||
However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
|
However, `go-json` can use the feature of `reflect.Type` while avoiding escaping.
|
||||||
|
|
||||||
`reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.
|
`reflect.Type` is defined as `interface`, but in reality `reflect.Type` is implemented only by the structure `rtype` defined in the `reflect` package.
|
||||||
|
|
|
||||||
31
vendor/github.com/goccy/go-json/decode.go
generated
vendored
31
vendor/github.com/goccy/go-json/decode.go
generated
vendored
|
|
@ -83,37 +83,6 @@ func unmarshalContext(ctx context.Context, data []byte, v interface{}, optFuncs
|
||||||
return validateEndBuf(src, cursor)
|
return validateEndBuf(src, cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
pathDecoder = decoder.NewPathDecoder()
|
|
||||||
)
|
|
||||||
|
|
||||||
func extractFromPath(path *Path, data []byte, optFuncs ...DecodeOptionFunc) ([][]byte, error) {
|
|
||||||
if path.path.RootSelectorOnly {
|
|
||||||
return [][]byte{data}, nil
|
|
||||||
}
|
|
||||||
src := make([]byte, len(data)+1) // append nul byte to the end
|
|
||||||
copy(src, data)
|
|
||||||
|
|
||||||
ctx := decoder.TakeRuntimeContext()
|
|
||||||
ctx.Buf = src
|
|
||||||
ctx.Option.Flags = 0
|
|
||||||
ctx.Option.Flags |= decoder.PathOption
|
|
||||||
ctx.Option.Path = path.path
|
|
||||||
for _, optFunc := range optFuncs {
|
|
||||||
optFunc(ctx.Option)
|
|
||||||
}
|
|
||||||
paths, cursor, err := pathDecoder.DecodePath(ctx, 0, 0)
|
|
||||||
if err != nil {
|
|
||||||
decoder.ReleaseRuntimeContext(ctx)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
decoder.ReleaseRuntimeContext(ctx)
|
|
||||||
if err := validateEndBuf(src, cursor); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return paths, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
func unmarshalNoEscape(data []byte, v interface{}, optFuncs ...DecodeOptionFunc) error {
|
||||||
src := make([]byte, len(data)+1) // append nul byte to the end
|
src := make([]byte, len(data)+1) // append nul byte to the end
|
||||||
copy(src, data)
|
copy(src, data)
|
||||||
|
|
|
||||||
2
vendor/github.com/goccy/go-json/docker-compose.yml
generated
vendored
2
vendor/github.com/goccy/go-json/docker-compose.yml
generated
vendored
|
|
@ -1,7 +1,7 @@
|
||||||
version: '2'
|
version: '2'
|
||||||
services:
|
services:
|
||||||
go-json:
|
go-json:
|
||||||
image: golang:1.18
|
image: golang:1.16
|
||||||
volumes:
|
volumes:
|
||||||
- '.:/go/src/go-json'
|
- '.:/go/src/go-json'
|
||||||
deploy:
|
deploy:
|
||||||
|
|
|
||||||
21
vendor/github.com/goccy/go-json/encode.go
generated
vendored
21
vendor/github.com/goccy/go-json/encode.go
generated
vendored
|
|
@ -3,7 +3,6 @@ package json
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/encoder"
|
"github.com/goccy/go-json/internal/encoder"
|
||||||
|
|
@ -52,7 +51,7 @@ func (e *Encoder) EncodeContext(ctx context.Context, v interface{}, optFuncs ...
|
||||||
rctx.Option.Flag |= encoder.ContextOption
|
rctx.Option.Flag |= encoder.ContextOption
|
||||||
rctx.Option.Context = ctx
|
rctx.Option.Context = ctx
|
||||||
|
|
||||||
err := e.encodeWithOption(rctx, v, optFuncs...) //nolint: contextcheck
|
err := e.encodeWithOption(rctx, v, optFuncs...)
|
||||||
|
|
||||||
encoder.ReleaseRuntimeContext(rctx)
|
encoder.ReleaseRuntimeContext(rctx)
|
||||||
return err
|
return err
|
||||||
|
|
@ -62,8 +61,6 @@ func (e *Encoder) encodeWithOption(ctx *encoder.RuntimeContext, v interface{}, o
|
||||||
if e.enabledHTMLEscape {
|
if e.enabledHTMLEscape {
|
||||||
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
||||||
}
|
}
|
||||||
ctx.Option.Flag |= encoder.NormalizeUTF8Option
|
|
||||||
ctx.Option.DebugOut = os.Stdout
|
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(ctx.Option)
|
optFunc(ctx.Option)
|
||||||
}
|
}
|
||||||
|
|
@ -114,13 +111,13 @@ func (e *Encoder) SetIndent(prefix, indent string) {
|
||||||
func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
|
func marshalContext(ctx context.Context, v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
|
||||||
rctx := encoder.TakeRuntimeContext()
|
rctx := encoder.TakeRuntimeContext()
|
||||||
rctx.Option.Flag = 0
|
rctx.Option.Flag = 0
|
||||||
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.ContextOption
|
rctx.Option.Flag = encoder.HTMLEscapeOption | encoder.ContextOption
|
||||||
rctx.Option.Context = ctx
|
rctx.Option.Context = ctx
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(rctx.Option)
|
optFunc(rctx.Option)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := encode(rctx, v) //nolint: contextcheck
|
buf, err := encode(rctx, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
encoder.ReleaseRuntimeContext(rctx)
|
encoder.ReleaseRuntimeContext(rctx)
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -142,7 +139,7 @@ func marshal(v interface{}, optFuncs ...EncodeOptionFunc) ([]byte, error) {
|
||||||
ctx := encoder.TakeRuntimeContext()
|
ctx := encoder.TakeRuntimeContext()
|
||||||
|
|
||||||
ctx.Option.Flag = 0
|
ctx.Option.Flag = 0
|
||||||
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
|
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(ctx.Option)
|
optFunc(ctx.Option)
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +166,7 @@ func marshalNoEscape(v interface{}) ([]byte, error) {
|
||||||
ctx := encoder.TakeRuntimeContext()
|
ctx := encoder.TakeRuntimeContext()
|
||||||
|
|
||||||
ctx.Option.Flag = 0
|
ctx.Option.Flag = 0
|
||||||
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option)
|
ctx.Option.Flag |= encoder.HTMLEscapeOption
|
||||||
|
|
||||||
buf, err := encodeNoEscape(ctx, v)
|
buf, err := encodeNoEscape(ctx, v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -193,7 +190,7 @@ func marshalIndent(v interface{}, prefix, indent string, optFuncs ...EncodeOptio
|
||||||
ctx := encoder.TakeRuntimeContext()
|
ctx := encoder.TakeRuntimeContext()
|
||||||
|
|
||||||
ctx.Option.Flag = 0
|
ctx.Option.Flag = 0
|
||||||
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.NormalizeUTF8Option | encoder.IndentOption)
|
ctx.Option.Flag |= (encoder.HTMLEscapeOption | encoder.IndentOption)
|
||||||
for _, optFunc := range optFuncs {
|
for _, optFunc := range optFuncs {
|
||||||
optFunc(ctx.Option)
|
optFunc(ctx.Option)
|
||||||
}
|
}
|
||||||
|
|
@ -223,7 +220,7 @@ func encode(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error) {
|
||||||
typ := header.typ
|
typ := header.typ
|
||||||
|
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
|
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -251,7 +248,7 @@ func encodeNoEscape(ctx *encoder.RuntimeContext, v interface{}) ([]byte, error)
|
||||||
typ := header.typ
|
typ := header.typ
|
||||||
|
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
|
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -278,7 +275,7 @@ func encodeIndent(ctx *encoder.RuntimeContext, v interface{}, prefix, indent str
|
||||||
typ := header.typ
|
typ := header.typ
|
||||||
|
|
||||||
typeptr := uintptr(unsafe.Pointer(typ))
|
typeptr := uintptr(unsafe.Pointer(typ))
|
||||||
codeSet, err := encoder.CompileToGetCodeSet(ctx, typeptr)
|
codeSet, err := encoder.CompileToGetCodeSet(typeptr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
2
vendor/github.com/goccy/go-json/error.go
generated
vendored
2
vendor/github.com/goccy/go-json/error.go
generated
vendored
|
|
@ -37,5 +37,3 @@ type UnmarshalTypeError = errors.UnmarshalTypeError
|
||||||
type UnsupportedTypeError = errors.UnsupportedTypeError
|
type UnsupportedTypeError = errors.UnsupportedTypeError
|
||||||
|
|
||||||
type UnsupportedValueError = errors.UnsupportedValueError
|
type UnsupportedValueError = errors.UnsupportedValueError
|
||||||
|
|
||||||
type PathError = errors.PathError
|
|
||||||
|
|
|
||||||
4
vendor/github.com/goccy/go-json/internal/decoder/anonymous_field.go
generated
vendored
4
vendor/github.com/goccy/go-json/internal/decoder/anonymous_field.go
generated
vendored
|
|
@ -35,7 +35,3 @@ func (d *anonymousFieldDecoder) Decode(ctx *RuntimeContext, cursor, depth int64,
|
||||||
p = *(*unsafe.Pointer)(p)
|
p = *(*unsafe.Pointer)(p)
|
||||||
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
|
return d.dec.Decode(ctx, cursor, depth, unsafe.Pointer(uintptr(p)+d.offset))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *anonymousFieldDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
|
||||||
return d.dec.DecodePath(ctx, cursor, depth)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
9
vendor/github.com/goccy/go-json/internal/decoder/array.go
generated
vendored
9
vendor/github.com/goccy/go-json/internal/decoder/array.go
generated
vendored
|
|
@ -1,7 +1,6 @@
|
||||||
package decoder
|
package decoder
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/goccy/go-json/internal/errors"
|
"github.com/goccy/go-json/internal/errors"
|
||||||
|
|
@ -19,9 +18,7 @@ type arrayDecoder struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder {
|
func newArrayDecoder(dec Decoder, elemType *runtime.Type, alen int, structName, fieldName string) *arrayDecoder {
|
||||||
// workaround to avoid checkptr errors. cannot use `*(*unsafe.Pointer)(unsafe_New(elemType))` directly.
|
zeroValue := *(*unsafe.Pointer)(unsafe_New(elemType))
|
||||||
zeroValuePtr := unsafe_New(elemType)
|
|
||||||
zeroValue := **(**unsafe.Pointer)(unsafe.Pointer(&zeroValuePtr))
|
|
||||||
return &arrayDecoder{
|
return &arrayDecoder{
|
||||||
valueDecoder: dec,
|
valueDecoder: dec,
|
||||||
elemType: elemType,
|
elemType: elemType,
|
||||||
|
|
@ -170,7 +167,3 @@ func (d *arrayDecoder) Decode(ctx *RuntimeContext, cursor, depth int64, p unsafe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *arrayDecoder) DecodePath(ctx *RuntimeContext, cursor, depth int64) ([][]byte, int64, error) {
|
|
||||||
return nil, 0, fmt.Errorf("json: array decoder does not support decode path")
|
|
||||||
}
|
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue