mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
fix(region): ssl certificate create (#22885)
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
func init() {
|
||||
cmd := shell.NewResourceCmd(&modules.SslCerticicate).WithKeyword("ssl-certificate")
|
||||
cmd.List(&compute.SslCertificateListOptions{})
|
||||
cmd.Create(&compute.SslCertificateCreateOptions{})
|
||||
cmd.Show(&options.BaseShowOptions{})
|
||||
cmd.Delete(&options.BaseIdOptions{})
|
||||
}
|
||||
|
||||
5
go.mod
5
go.mod
@@ -18,6 +18,7 @@ require (
|
||||
github.com/creack/pty v1.1.18
|
||||
github.com/docker/docker v17.12.0-ce-rc1.0.20200916142827-bd33bbf0497b+incompatible
|
||||
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96
|
||||
github.com/eggsampler/acme/v3 v3.6.2
|
||||
github.com/fernet/fernet-go v0.0.0-20180830025343-9eac43b88a5e
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
@@ -95,12 +96,12 @@ require (
|
||||
k8s.io/cri-api v0.22.17
|
||||
k8s.io/klog/v2 v2.20.0
|
||||
moul.io/http2curl/v2 v2.3.0
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250704072708-d4757966c574
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250714064001-1e1f50030bed
|
||||
yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0
|
||||
yunion.io/x/jsonutils v1.0.1-0.20250507052344-1abcf4f443b1
|
||||
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91
|
||||
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900
|
||||
yunion.io/x/pkg v1.10.4-0.20250618071935-840c253075b0
|
||||
yunion.io/x/pkg v1.10.4-0.20250712095850-7797c1f3ffa6
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20250531010554-ce98f840b833
|
||||
yunion.io/x/structarg v0.0.0-20231017124457-df4d5009457c
|
||||
|
||||
10
go.sum
10
go.sum
@@ -295,6 +295,8 @@ github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFP
|
||||
github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/edsrzf/mmap-go v1.0.0 h1:CEBF7HpRnUCSJgGUb5h1Gm7e3VkmVDrR8lvWVLtrOFw=
|
||||
github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
|
||||
github.com/eggsampler/acme/v3 v3.6.2 h1:gvyZbQ92wNQLDASVftGpHEdFwPSfg0+17P0lLt09Tp8=
|
||||
github.com/eggsampler/acme/v3 v3.6.2/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo=
|
||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||
github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a h1:2Zw3pxDRTs4nX1WCLAEm27UN0hvjZSge7EaUUQexRZw=
|
||||
github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a/go.mod h1:afkYpY8JAIL4341N7Zj9xJ5yTovsg6BkWfBFlCzIoF4=
|
||||
@@ -1406,8 +1408,8 @@ sigs.k8s.io/structured-merge-diff/v4 v4.0.1/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK
|
||||
sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
|
||||
sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250704072708-d4757966c574 h1:kglRbPc1P9I7rt3DP7O4pc8ji2JqVvZ4GSD+j52fwSI=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250704072708-d4757966c574/go.mod h1:FXxAEbdNfWXX9gjME3K2nJhkydHY5EKEUZb+RLEzVwQ=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250714064001-1e1f50030bed h1:L34xL83eV2RzpwCjfzOE7ls/5u5EvEjqAL2ToTaD6Tc=
|
||||
yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250714064001-1e1f50030bed/go.mod h1:FXxAEbdNfWXX9gjME3K2nJhkydHY5EKEUZb+RLEzVwQ=
|
||||
yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0 h1:msG4SiDSVU7CrXH06WuHlNEZXIooTcmNbfrIGHuIHBU=
|
||||
yunion.io/x/executor v0.0.0-20250518005516-5402e9e0bed0/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
|
||||
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
|
||||
@@ -1421,8 +1423,8 @@ yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900 h1:Hu/4ERvoWaN6aiFs4h4/yvVB
|
||||
yunion.io/x/ovsdb v0.0.0-20230306173834-f164f413a900/go.mod h1:0vLkNEhlmA64HViPBAnSTUMrx5QP1CLsxXmxDKQ80tc=
|
||||
yunion.io/x/pkg v0.0.0-20190620104149-945c25821dbf/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v0.0.0-20190628082551-f4033ba2ea30/go.mod h1:t6rEGG2sQ4J7DhFxSZVOTjNd0YO/KlfWQyK1W4tog+E=
|
||||
yunion.io/x/pkg v1.10.4-0.20250618071935-840c253075b0 h1:diTsDq4t6jntGdAKtHZYx9eRWkLg0KyMTp0WFdueT+0=
|
||||
yunion.io/x/pkg v1.10.4-0.20250618071935-840c253075b0/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
|
||||
yunion.io/x/pkg v1.10.4-0.20250712095850-7797c1f3ffa6 h1:CzEjNUur33HGmDkW7uKw1eU97gwojNsXlB2AMEzItn8=
|
||||
yunion.io/x/pkg v1.10.4-0.20250712095850-7797c1f3ffa6/go.mod h1:0Bwxqd9MA3ACi119/l02FprY/o9gHahmYC2bsSbnVpM=
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1 h1:1KJ3YYinydPHpDEQRXdr/T8SYcKZ5Er+m489H+PnaQ4=
|
||||
yunion.io/x/s3cli v0.0.0-20241221171442-1c11599d28e1/go.mod h1:0iFKpOs1y4lbCxeOmq3Xx/0AcQoewVPwj62eRluioEo=
|
||||
yunion.io/x/sqlchemy v1.1.3-0.20250531010554-ce98f840b833 h1:XTFC1naKYkciCQDLm9izpzHXfTenmmtYsTpVKrsN5hE=
|
||||
|
||||
@@ -14,10 +14,31 @@
|
||||
|
||||
package compute
|
||||
|
||||
import "yunion.io/x/onecloud/pkg/apis"
|
||||
import (
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
)
|
||||
|
||||
const (
|
||||
SSL_ISSUER_LETSENCRYPT = cloudprovider.SSL_ISSUER_LETSENCRYPT
|
||||
SSL_ISSUER_ZEROSSL = cloudprovider.SSL_ISSUER_ZEROSSL
|
||||
)
|
||||
|
||||
// 资源创建参数, 目前仅占位
|
||||
type SSLCertificateCreateInput struct {
|
||||
apis.VirtualResourceCreateInput
|
||||
|
||||
Issuer string `json:"issuer"`
|
||||
DnsZoneId string `json:"dns_zone_id"`
|
||||
Sans string `json:"sans"`
|
||||
|
||||
Province string
|
||||
Common string
|
||||
Country string
|
||||
City string
|
||||
OrgName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
// 资源返回详情
|
||||
|
||||
@@ -24,10 +24,13 @@ import (
|
||||
"yunion.io/x/pkg/util/compare"
|
||||
"yunion.io/x/sqlchemy"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/lockman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/notifyclient"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
|
||||
"yunion.io/x/onecloud/pkg/httperrors"
|
||||
"yunion.io/x/onecloud/pkg/mcclient"
|
||||
"yunion.io/x/onecloud/pkg/util/stringutils2"
|
||||
@@ -67,19 +70,18 @@ type SSSLCertificate struct {
|
||||
SDeletePreventableResourceBase
|
||||
|
||||
Sans string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
StartDate time.Time `nullable:"false" list:"user" json:"start_date"`
|
||||
Province string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
Common string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
Country string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
Issuer string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
Expired bool `charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
IsUpload bool `charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
EndDate time.Time `nullable:"false" list:"user" json:"end_date"`
|
||||
Fingerprint string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
City string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
OrgName string `width:"2048" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
Certificate string `charset:"utf8" nullable:"true" list:"user" create:"required"`
|
||||
PrivateKey string `charset:"utf8" nullable:"true" list:"user" create:"required"`
|
||||
StartDate time.Time `list:"user"`
|
||||
Province string `width:"64" charset:"utf8" nullable:"false" list:"user" create:"optional"`
|
||||
Common string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"optional"`
|
||||
Country string `width:"32" charset:"utf8" nullable:"false" list:"user" create:"optional"`
|
||||
Issuer string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"required"`
|
||||
IsUpload bool `list:"user" create:"optional"`
|
||||
EndDate time.Time `list:"user" create:"optional"`
|
||||
Fingerprint string `width:"128" charset:"utf8" nullable:"false" list:"user" create:"optional"`
|
||||
City string `width:"64" charset:"utf8" nullable:"false" list:"user" create:"optional"`
|
||||
OrgName string `width:"256" charset:"utf8" nullable:"false" list:"user" create:"optional"`
|
||||
Certificate string `charset:"utf8" nullable:"true" list:"user" create:"optional"`
|
||||
PrivateKey string `charset:"utf8" nullable:"true" list:"user" create:"optional"`
|
||||
}
|
||||
|
||||
func (s SSSLCertificate) GetExternalId() string {
|
||||
@@ -188,8 +190,53 @@ func (man *SSSLCertificateManager) QueryDistinctExtraField(q *sqlchemy.SQuery, f
|
||||
return q, httperrors.ErrNotFound
|
||||
}
|
||||
|
||||
func (man *SSSLCertificateManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.SSLCertificateCreateInput) (api.SSLCertificateCreateInput, error) {
|
||||
return input, httperrors.NewNotImplementedError("Not Implemented")
|
||||
func (man *SSSLCertificateManager) ValidateCreateData(
|
||||
ctx context.Context,
|
||||
userCred mcclient.TokenCredential,
|
||||
ownerId mcclient.IIdentityProvider,
|
||||
query jsonutils.JSONObject,
|
||||
input *api.SSLCertificateCreateInput,
|
||||
) (*api.SSLCertificateCreateInput, error) {
|
||||
if len(input.DnsZoneId) > 0 {
|
||||
obj, err := validators.ValidateModel(ctx, userCred, DnsZoneManager, &input.DnsZoneId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
input.DnsZoneId = obj.GetId()
|
||||
}
|
||||
var err error
|
||||
input.VirtualResourceCreateInput, err = man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.VirtualResourceCreateInput)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return input, nil
|
||||
}
|
||||
|
||||
func (self *SSSLCertificate) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
|
||||
self.SVirtualResourceBase.PostCreate(ctx, userCred, ownerId, query, data)
|
||||
self.StartSSLCertificateCreateTask(ctx, userCred, "")
|
||||
}
|
||||
|
||||
func (self *SSSLCertificate) StartSSLCertificateCreateTask(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error {
|
||||
params := jsonutils.NewDict()
|
||||
task, err := taskman.TaskManager.NewTask(ctx, "SSLCertificateCreateTask", self, userCred, params, parentTaskId, "", nil)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "NewTask")
|
||||
}
|
||||
self.SetStatus(ctx, userCred, apis.STATUS_CREATING, "")
|
||||
return task.ScheduleRun(nil)
|
||||
}
|
||||
|
||||
func (self *SSSLCertificate) GetDnsZone() (*SDnsZone, error) {
|
||||
if len(self.DnsZoneId) == 0 {
|
||||
return nil, errors.Wrapf(cloudprovider.ErrNotFound, "DnsZoneId is empty")
|
||||
}
|
||||
zone, err := DnsZoneManager.FetchById(self.DnsZoneId)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "DnsZoneManager.FetchById")
|
||||
}
|
||||
return zone.(*SDnsZone), nil
|
||||
}
|
||||
|
||||
func (r *SCloudprovider) GetSSLCertificates() ([]SSSLCertificate, error) {
|
||||
@@ -290,7 +337,7 @@ func (s *SSSLCertificate) SyncWithCloudSSLCertificate(ctx context.Context, userC
|
||||
s.Common = ext.GetCommon()
|
||||
s.Country = ext.GetCountry()
|
||||
s.Issuer = ext.GetIssuer()
|
||||
s.Expired = ext.GetExpired()
|
||||
//s.Expired = ext.GetExpired()
|
||||
s.IsUpload = ext.GetIsUpload()
|
||||
s.EndDate = ext.GetEndDate()
|
||||
s.Fingerprint = ext.GetFingerprint()
|
||||
@@ -349,7 +396,7 @@ func (r *SCloudprovider) newFromCloudSSLCertificate(
|
||||
s.Common = ext.GetCommon()
|
||||
s.Country = ext.GetCountry()
|
||||
s.Issuer = ext.GetIssuer()
|
||||
s.Expired = ext.GetExpired()
|
||||
//s.Expired = ext.GetExpired()
|
||||
s.IsUpload = ext.GetIsUpload()
|
||||
s.EndDate = ext.GetEndDate()
|
||||
s.Fingerprint = ext.GetFingerprint()
|
||||
|
||||
@@ -235,6 +235,8 @@ type ComputeOptions struct {
|
||||
|
||||
NetworkAlwaysManualConfig bool `help:"always manually configure network settings" default:"false"`
|
||||
|
||||
SSLAccounts []string `help:"SSL account"`
|
||||
|
||||
ComputeEEOptions
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ import (
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/server_sku"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/snapshot"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/snapshotpolicy"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/ssl_certificate"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/storage"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/vpc"
|
||||
_ "yunion.io/x/onecloud/pkg/compute/tasks/vpc_peering"
|
||||
|
||||
1
pkg/compute/tasks/ssl_certificate/doc.go
Normal file
1
pkg/compute/tasks/ssl_certificate/doc.go
Normal file
@@ -0,0 +1 @@
|
||||
package ssl_certificate // import "yunion.io/x/onecloud/pkg/compute/tasks/ssl_certificate"
|
||||
314
pkg/compute/tasks/ssl_certificate/ssl_certificate_create_task.go
Normal file
314
pkg/compute/tasks/ssl_certificate/ssl_certificate_create_task.go
Normal file
@@ -0,0 +1,314 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package ssl_certificate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/eggsampler/acme/v3"
|
||||
|
||||
"yunion.io/x/cloudmux/pkg/cloudprovider"
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
"yunion.io/x/onecloud/pkg/apis"
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
"yunion.io/x/onecloud/pkg/appsrv"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/db/taskman"
|
||||
"yunion.io/x/onecloud/pkg/compute/models"
|
||||
"yunion.io/x/onecloud/pkg/compute/options"
|
||||
"yunion.io/x/onecloud/pkg/util/logclient"
|
||||
)
|
||||
|
||||
var (
|
||||
SSLCertificateCreateWorkerManager *appsrv.SWorkerManager
|
||||
)
|
||||
|
||||
func init() {
|
||||
SSLCertificateCreateWorkerManager = appsrv.NewWorkerManager("SSLCertificateCreateWorkerManager", 8, 1024, false)
|
||||
taskman.RegisterTaskAndWorker(SSLCertificateCreateTask{}, SSLCertificateCreateWorkerManager)
|
||||
}
|
||||
|
||||
type SSLCertificateCreateTask struct {
|
||||
taskman.STask
|
||||
}
|
||||
|
||||
func (self *SSLCertificateCreateTask) taskFailed(ctx context.Context, sc *models.SSSLCertificate, err error) {
|
||||
sc.SetStatus(ctx, self.UserCred, apis.STATUS_CREATE_FAILED, err.Error())
|
||||
db.OpsLog.LogEvent(sc, db.ACT_ALLOCATE_FAIL, err, self.UserCred)
|
||||
logclient.AddActionLogWithStartable(self, sc, logclient.ACT_ALLOCATE, err, self.UserCred, false)
|
||||
self.SetStageFailed(ctx, jsonutils.NewString(err.Error()))
|
||||
}
|
||||
|
||||
func key2pem(certKey *ecdsa.PrivateKey) ([]byte, error) {
|
||||
certKeyEnc, err := x509.MarshalECPrivateKey(certKey)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "MarshalECPrivateKey")
|
||||
}
|
||||
|
||||
return pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: certKeyEnc,
|
||||
}), nil
|
||||
}
|
||||
|
||||
func (self *SSLCertificateCreateTask) OnInit(ctx context.Context, obj db.IStandaloneModel, body jsonutils.JSONObject) {
|
||||
sc := obj.(*models.SSSLCertificate)
|
||||
|
||||
zone, err := sc.GetDnsZone()
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "GetDnsZone"))
|
||||
return
|
||||
}
|
||||
|
||||
iZone, err := zone.GetICloudDnsZone(ctx)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "GetProvider"))
|
||||
return
|
||||
}
|
||||
if len(sc.Issuer) == 0 {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "Issuer is required"))
|
||||
return
|
||||
}
|
||||
|
||||
addr := ""
|
||||
switch sc.Issuer {
|
||||
case api.SSL_ISSUER_LETSENCRYPT:
|
||||
addr = acme.LetsEncryptStaging
|
||||
case api.SSL_ISSUER_ZEROSSL:
|
||||
addr = acme.ZeroSSLProduction
|
||||
}
|
||||
|
||||
client, err := acme.NewClient(addr)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "NewClient"))
|
||||
return
|
||||
}
|
||||
client.PollInterval = 10 * time.Second
|
||||
client.PollTimeout = 6 * time.Minute
|
||||
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "GenerateKey"))
|
||||
return
|
||||
}
|
||||
|
||||
emails := []string{}
|
||||
for _, email := range options.Options.SSLAccounts {
|
||||
emails = append(emails, "mailto:"+email)
|
||||
}
|
||||
account, err := client.NewAccount(privKey, false, true, emails...)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "NewAccount"))
|
||||
return
|
||||
}
|
||||
|
||||
domainList := strings.Split(sc.Sans, ",")
|
||||
var ids []acme.Identifier
|
||||
for _, domain := range domainList {
|
||||
ids = append(ids, acme.Identifier{Type: "dns", Value: domain})
|
||||
}
|
||||
|
||||
order, err := client.NewOrder(account, ids)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "NewOrder"))
|
||||
return
|
||||
}
|
||||
|
||||
for _, authUrl := range order.Authorizations {
|
||||
auth, err := client.FetchAuthorization(account, authUrl)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "FetchAuthorization"))
|
||||
return
|
||||
}
|
||||
chal, ok := auth.ChallengeMap[acme.ChallengeTypeDNS01]
|
||||
if !ok {
|
||||
self.taskFailed(ctx, sc, fmt.Errorf("ChallengeTypeDNS01 not found"))
|
||||
return
|
||||
}
|
||||
|
||||
txt := acme.EncodeDNS01KeyAuthorization(chal.KeyAuthorization)
|
||||
|
||||
info := strings.Split(auth.Identifier.Value, ".")
|
||||
subDomain := strings.Join(info[:len(info)-2], ".")
|
||||
subDomain = strings.ReplaceAll(subDomain, "*", "")
|
||||
dnsName := "_acme-challenge"
|
||||
if len(subDomain) > 0 {
|
||||
dnsName = dnsName + "." + subDomain
|
||||
}
|
||||
|
||||
log.Debugf("add dns record for %s: label: %s, txt: %s", auth.Identifier.Value, dnsName, txt)
|
||||
|
||||
err = func() error {
|
||||
opts := &cloudprovider.DnsRecord{
|
||||
DnsName: dnsName,
|
||||
DnsValue: txt,
|
||||
DnsType: cloudprovider.DnsTypeTXT,
|
||||
Enabled: true,
|
||||
Ttl: 60,
|
||||
}
|
||||
recordId, err := iZone.AddDnsRecord(opts)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "AddDnsRecord")
|
||||
}
|
||||
|
||||
cloudprovider.Wait(10*time.Second, 3*time.Minute, func() (bool, error) {
|
||||
v, err := net.LookupTXT("_acme-challenge." + auth.Identifier.Value)
|
||||
log.Debugf("lookup txt for %s: %s, error: %v", "_acme-challenge."+auth.Identifier.Value, v, err)
|
||||
if len(v) > 0 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
})
|
||||
|
||||
defer func() {
|
||||
record, err := iZone.GetIDnsRecordById(recordId)
|
||||
if err != nil {
|
||||
logclient.AddActionLogWithStartable(self, sc, logclient.ACT_UPDATE, errors.Wrapf(err, "GetIDnsRecordById"), self.UserCred, false)
|
||||
return
|
||||
}
|
||||
err = record.Delete()
|
||||
if err != nil {
|
||||
logclient.AddActionLogWithStartable(self, sc, logclient.ACT_UPDATE, errors.Wrapf(err, "Delete"), self.UserCred, false)
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
chal, err = client.UpdateChallenge(account, chal)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "UpdateChallenge")
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "AddDnsRecord"))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "GenerateKey"))
|
||||
return
|
||||
}
|
||||
|
||||
b, err := key2pem(certKey)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "key2pem"))
|
||||
return
|
||||
}
|
||||
|
||||
tpl := &x509.CertificateRequest{
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
PublicKeyAlgorithm: x509.ECDSA,
|
||||
PublicKey: certKey.Public(),
|
||||
Subject: pkix.Name{CommonName: domainList[0]},
|
||||
DNSNames: domainList,
|
||||
}
|
||||
csrDer, err := x509.CreateCertificateRequest(rand.Reader, tpl, certKey)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "CreateCertificateRequest"))
|
||||
return
|
||||
}
|
||||
csr, err := x509.ParseCertificateRequest(csrDer)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "ParseCertificateRequest"))
|
||||
return
|
||||
}
|
||||
|
||||
order, err = client.FinalizeOrder(account, order, csr)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "FinalizeOrder"))
|
||||
return
|
||||
}
|
||||
|
||||
certs, err := client.FetchCertificates(account, order.Certificate)
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "FetchCertificates"))
|
||||
return
|
||||
}
|
||||
|
||||
start, end, country, province, city := time.Time{}, time.Time{}, "", "", ""
|
||||
var pemData []string
|
||||
for _, c := range certs {
|
||||
start = c.NotBefore
|
||||
end = c.NotAfter
|
||||
if len(c.Subject.Country) > 0 {
|
||||
country = c.Subject.Country[0]
|
||||
}
|
||||
if len(c.Subject.Province) > 0 {
|
||||
province = c.Subject.Province[0]
|
||||
}
|
||||
if len(c.Subject.Locality) > 0 {
|
||||
city = c.Subject.Locality[0]
|
||||
}
|
||||
pemData = append(pemData, strings.TrimSpace(string(pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: c.Raw,
|
||||
}))))
|
||||
}
|
||||
|
||||
_, err = db.Update(sc, func() error {
|
||||
sc.Certificate = strings.Join(pemData, "\n")
|
||||
sc.PrivateKey = string(b)
|
||||
sc.EndDate = end
|
||||
sc.StartDate = start
|
||||
sc.Country = country
|
||||
sc.Province = province
|
||||
sc.City = city
|
||||
sc.Status = apis.STATUS_AVAILABLE
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
self.taskFailed(ctx, sc, errors.Wrapf(err, "Update"))
|
||||
return
|
||||
}
|
||||
|
||||
err = func() error {
|
||||
provider, err := zone.GetProvider(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "GetProvider")
|
||||
}
|
||||
opts := &cloudprovider.SSLCertificateCreateOptions{
|
||||
Certificate: sc.Certificate,
|
||||
PrivateKey: sc.PrivateKey,
|
||||
}
|
||||
_, err = provider.CreateISSLCertificate(opts)
|
||||
if err != nil {
|
||||
if errors.Cause(err) == cloudprovider.ErrNotImplemented || errors.Cause(err) == cloudprovider.ErrNotSupported {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "CreateISSLCertificate")
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
logclient.AddActionLogWithStartable(self, sc, logclient.ACT_CREATE, err, self.UserCred, false)
|
||||
}
|
||||
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
@@ -27,3 +27,23 @@ type SslCertificateListOptions struct {
|
||||
func (opts *SslCertificateListOptions) Params() (jsonutils.JSONObject, error) {
|
||||
return options.ListStructToParams(opts)
|
||||
}
|
||||
|
||||
type SslCertificateCreateOptions struct {
|
||||
options.BaseCreateOptions
|
||||
|
||||
Issuer string `json:"issuer"`
|
||||
DnsZoneId string `json:"dns_zone_id"`
|
||||
Sans string `json:"sans"`
|
||||
|
||||
Province string
|
||||
Common string
|
||||
Country string
|
||||
City string
|
||||
OrgName string
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
}
|
||||
|
||||
func (opts *SslCertificateCreateOptions) Params() (jsonutils.JSONObject, error) {
|
||||
return jsonutils.Marshal(opts), nil
|
||||
}
|
||||
|
||||
3
vendor/github.com/eggsampler/acme/v3/.gitignore
generated
vendored
Normal file
3
vendor/github.com/eggsampler/acme/v3/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
.idea/
|
||||
*.out
|
||||
coverage*
|
||||
21
vendor/github.com/eggsampler/acme/v3/LICENSE
generated
vendored
Normal file
21
vendor/github.com/eggsampler/acme/v3/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 Isaac
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
75
vendor/github.com/eggsampler/acme/v3/Makefile
generated
vendored
Normal file
75
vendor/github.com/eggsampler/acme/v3/Makefile
generated
vendored
Normal file
@@ -0,0 +1,75 @@
|
||||
|
||||
.PHONY: test examples clean test_full pebble pebble_setup pebble_start pebble_wait pebble_stop boulder boulder_setup boulder_start boulder_stop
|
||||
|
||||
# some variables for path injection, if already set will not override
|
||||
GOPATH ?= $(HOME)/go
|
||||
BOULDER_PATH ?= $(GOPATH)/src/github.com/letsencrypt/boulder
|
||||
PEBBLE_PATH ?= $(GOPATH)/src/github.com/letsencrypt/pebble
|
||||
TEST_PATH ?= github.com/eggsampler/acme/v3
|
||||
CLIENT ?= unknown
|
||||
|
||||
# tests the code against an already running ca instance
|
||||
# to actually do a test against pebble or boulder, including , see the 'pebble' or 'boulder' targets
|
||||
test:
|
||||
-go clean -testcache
|
||||
CGO_ENABLED=1 go test -v -race -coverprofile=coverage-$(CLIENT).out -covermode=atomic $(TEST_PATH)
|
||||
|
||||
examples:
|
||||
go build -o /dev/null examples/certbot/certbot.go
|
||||
go build -o /dev/null examples/autocert/autocert.go
|
||||
go build -o /dev/null examples/zerossl/zerossl.go
|
||||
go build -o /dev/null examples/ari/renewalinfo.go
|
||||
|
||||
clean:
|
||||
rm -f coverage*.out
|
||||
|
||||
test_full: clean examples pebble pebble_stop boulder boulder_stop
|
||||
|
||||
|
||||
# sets up & runs pebble (in docker), tests, then stops pebble
|
||||
pebble: CLIENT = pebble
|
||||
pebble: pebble_setup pebble_start pebble_wait test pebble_stop
|
||||
|
||||
pebble_setup:
|
||||
CLIENT=pebble
|
||||
mkdir -p $(PEBBLE_PATH)
|
||||
-git clone --depth 1 https://github.com/letsencrypt/pebble.git $(PEBBLE_PATH)
|
||||
(cd $(PEBBLE_PATH); git checkout -f main && git reset --hard HEAD && git pull -q)
|
||||
make pebble_stop
|
||||
|
||||
# runs an instance of pebble using docker
|
||||
pebble_start:
|
||||
docker-compose -f $(PEBBLE_PATH)/docker-compose.yml up -d
|
||||
|
||||
# waits until pebble responds
|
||||
pebble_wait:
|
||||
while ! wget --delete-after -q --no-check-certificate "https://localhost:14000/dir" ; do sleep 1 ; done
|
||||
|
||||
# stops the running pebble instance
|
||||
pebble_stop:
|
||||
docker-compose -f $(PEBBLE_PATH)/docker-compose.yml down
|
||||
|
||||
|
||||
# sets up & runs boulder (in docker), tests, then stops boulder
|
||||
boulder: CLIENT = boulder
|
||||
boulder: boulder_setup boulder_start boulder_wait test boulder_stop
|
||||
|
||||
# NB: this edits docker-compose.yml
|
||||
boulder_setup:
|
||||
mkdir -p $(BOULDER_PATH)
|
||||
-git clone --depth 1 https://github.com/letsencrypt/boulder.git $(BOULDER_PATH)
|
||||
(cd $(BOULDER_PATH); git checkout -f main && git reset --hard HEAD && git pull -q)
|
||||
make boulder_stop
|
||||
(cd $(BOULDER_PATH); docker compose run --rm bsetup)
|
||||
|
||||
# runs an instance of boulder
|
||||
boulder_start:
|
||||
docker-compose -f $(BOULDER_PATH)/docker-compose.yml -f $(BOULDER_PATH)/docker-compose.next.yml -f docker-compose.boulder-temp.yml up -d
|
||||
|
||||
# waits until boulder responds
|
||||
boulder_wait:
|
||||
while ! wget --delete-after -q --no-check-certificate "http://localhost:4001/directory" ; do sleep 1 ; done
|
||||
|
||||
# stops the running docker instance
|
||||
boulder_stop:
|
||||
docker-compose -f $(BOULDER_PATH)/docker-compose.yml -f $(BOULDER_PATH)/docker-compose.next.yml -f docker-compose.boulder-temp.yml down
|
||||
43
vendor/github.com/eggsampler/acme/v3/README.md
generated
vendored
Normal file
43
vendor/github.com/eggsampler/acme/v3/README.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# eggsampler/acme
|
||||
|
||||
[](https://godoc.org/github.com/eggsampler/acme)
|
||||
[](https://github.com/eggsampler/acme/actions)
|
||||
[](https://coveralls.io/github/eggsampler/acme)
|
||||
|
||||
## About
|
||||
|
||||
`eggsampler/acme` is a Go client library implementation for [RFC8555](https://tools.ietf.org/html/rfc8555) (previously ACME v2). This library can be used with the [Let's Encrypt](https://letsencrypt.org/) Certificate Authority (CA), but also other ACME compliant CA's such as [ZeroSSL](https://zerossl.com/).
|
||||
|
||||
The library is designed to provide a zero external dependency wrapper over exposed directory endpoints and provide objects in easy to use structures.
|
||||
|
||||
## Requirements
|
||||
|
||||
A Go version of at least 1.11 is required as this repository is designed to be imported as a Go module.
|
||||
|
||||
## Usage
|
||||
|
||||
Simply import the module into a project,
|
||||
|
||||
```go
|
||||
import "github.com/eggsampler/acme/v3"
|
||||
```
|
||||
|
||||
Note the `/v3` major version at the end. Due to the way modules function, this is the major version as represented in the `go.mod` file and latest git repo [semver](https://semver.org/) tag.
|
||||
All functions are still exported and called using the `acme` package name.
|
||||
|
||||
## Examples
|
||||
|
||||
A simple [certbot](https://certbot.eff.org/)-like example is provided in the examples/certbot directory.
|
||||
This code demonstrates account registration, new order submission, fulfilling challenges, finalising an order and fetching the issued certificate chain.
|
||||
|
||||
An example of how to use the autocert package is also provided in examples/autocert.
|
||||
|
||||
## Tests
|
||||
|
||||
The tests can be run against an instance of [boulder](https://github.com/letsencrypt/boulder) or [pebble](https://github.com/letsencrypt/pebble).
|
||||
|
||||
Challenge fulfilment is designed to use the new `challtestsrv` server present inside boulder and pebble which responds to dns queries and challenges as required.
|
||||
|
||||
To run tests against an already running instance of boulder or pebble, use the `test` target in the Makefile.
|
||||
|
||||
Some convenience targets for launching pebble/boulder using their respective docker compose files have also been included in the Makefile.
|
||||
35
vendor/github.com/eggsampler/acme/v3/THIRD-PARTY
generated
vendored
Normal file
35
vendor/github.com/eggsampler/acme/v3/THIRD-PARTY
generated
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
This document contains Third Party Software Notices and/or Additional Terms and Conditions for licensed third party software components included within this product.
|
||||
|
||||
==
|
||||
|
||||
https://github.com/golang/crypto/blob/master/acme/jws.go
|
||||
https://github.com/golang/crypto/blob/master/acme/jws_test.go
|
||||
(with modifications)
|
||||
|
||||
Copyright (c) 2009 The Go Authors. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
144
vendor/github.com/eggsampler/acme/v3/account.go
generated
vendored
Normal file
144
vendor/github.com/eggsampler/acme/v3/account.go
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
// NewAccount registers a new account with the acme service
|
||||
// Note this function is essentially deprecated and only present for backwards compatibility.
|
||||
// New programs should implement NewAccountOptions instead.
|
||||
func (c Client) NewAccount(privateKey crypto.Signer, onlyReturnExisting, termsOfServiceAgreed bool, contact ...string) (Account, error) {
|
||||
var opts []NewAccountOptionFunc
|
||||
if onlyReturnExisting {
|
||||
opts = append(opts, NewAcctOptOnlyReturnExisting())
|
||||
}
|
||||
if termsOfServiceAgreed {
|
||||
opts = append(opts, NewAcctOptAgreeTOS())
|
||||
}
|
||||
if len(contact) > 0 {
|
||||
opts = append(opts, NewAcctOptWithContacts(contact...))
|
||||
}
|
||||
|
||||
return c.NewAccountOptions(privateKey, opts...)
|
||||
}
|
||||
|
||||
// NewAccountOptions registers an account with an acme server with the provided options.
|
||||
func (c Client) NewAccountOptions(privateKey crypto.Signer, options ...NewAccountOptionFunc) (Account, error) {
|
||||
newAccountReq := NewAccountRequest{}
|
||||
account := Account{}
|
||||
|
||||
for _, opt := range options {
|
||||
if err := opt(privateKey, &account, &newAccountReq, c); err != nil {
|
||||
return account, err
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := c.post(c.dir.NewAccount, "", privateKey, newAccountReq, &account, http.StatusOK, http.StatusCreated)
|
||||
if err != nil {
|
||||
return account, err
|
||||
}
|
||||
|
||||
account.URL = resp.Header.Get("Location")
|
||||
account.PrivateKey = privateKey
|
||||
|
||||
if account.Thumbprint == "" {
|
||||
account.Thumbprint, err = JWKThumbprint(account.PrivateKey.Public())
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("acme: error computing account thumbprint: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// UpdateAccount updates an existing account with the acme service.
|
||||
func (c Client) UpdateAccount(account Account, contact ...string) (Account, error) {
|
||||
var updateAccountReq interface{}
|
||||
|
||||
if !reflect.DeepEqual(account.Contact, contact) {
|
||||
// Only provide a non-nil updateAccountReq when there is an update to be made.
|
||||
updateAccountReq = struct {
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
}{
|
||||
Contact: contact,
|
||||
}
|
||||
} else {
|
||||
// Otherwise use "" to trigger a POST-as-GET to fetch up-to-date account
|
||||
// information from the acme service.
|
||||
updateAccountReq = ""
|
||||
}
|
||||
|
||||
_, err := c.post(account.URL, account.URL, account.PrivateKey, updateAccountReq, &account, http.StatusOK)
|
||||
if err != nil {
|
||||
return account, err
|
||||
}
|
||||
|
||||
if account.Thumbprint == "" {
|
||||
account.Thumbprint, err = JWKThumbprint(account.PrivateKey.Public())
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("acme: error computing account thumbprint: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// AccountKeyChange rolls over an account to a new key.
|
||||
func (c Client) AccountKeyChange(account Account, newPrivateKey crypto.Signer) (Account, error) {
|
||||
oldJwkKeyPub, err := jwkEncode(account.PrivateKey.Public())
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("acme: error encoding new private key: %v", err)
|
||||
}
|
||||
|
||||
keyChangeReq := struct {
|
||||
Account string `json:"account"`
|
||||
OldKey json.RawMessage `json:"oldKey"`
|
||||
}{
|
||||
Account: account.URL,
|
||||
OldKey: []byte(oldJwkKeyPub),
|
||||
}
|
||||
|
||||
innerJws, err := jwsEncodeJSON(keyChangeReq, newPrivateKey, "", "", c.dir.KeyChange)
|
||||
if err != nil {
|
||||
return account, fmt.Errorf("acme: error encoding inner jws: %v", err)
|
||||
}
|
||||
|
||||
if _, err := c.post(c.dir.KeyChange, account.URL, account.PrivateKey, json.RawMessage(innerJws), nil, http.StatusOK); err != nil {
|
||||
return account, err
|
||||
}
|
||||
|
||||
account.PrivateKey = newPrivateKey
|
||||
|
||||
return account, nil
|
||||
}
|
||||
|
||||
// DeactivateAccount deactivates a given account.
|
||||
func (c Client) DeactivateAccount(account Account) (Account, error) {
|
||||
deactivateReq := struct {
|
||||
Status string `json:"status"`
|
||||
}{
|
||||
Status: "deactivated",
|
||||
}
|
||||
|
||||
_, err := c.post(account.URL, account.URL, account.PrivateKey, deactivateReq, &account, http.StatusOK)
|
||||
|
||||
return account, err
|
||||
}
|
||||
|
||||
// FetchOrderList fetches a list of orders from the account url provided in the account Orders field
|
||||
func (c Client) FetchOrderList(account Account) (OrderList, error) {
|
||||
orderList := OrderList{}
|
||||
|
||||
if account.Orders == "" {
|
||||
return orderList, errors.New("no order list for account")
|
||||
}
|
||||
|
||||
_, err := c.post(account.Orders, account.URL, account.PrivateKey, "", &orderList, http.StatusOK)
|
||||
|
||||
return orderList, err
|
||||
}
|
||||
303
vendor/github.com/eggsampler/acme/v3/acme.go
generated
vendored
Normal file
303
vendor/github.com/eggsampler/acme/v3/acme.go
generated
vendored
Normal file
@@ -0,0 +1,303 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// LetsEncryptProduction holds the production directory url
|
||||
LetsEncryptProduction = "https://acme-v02.api.letsencrypt.org/directory"
|
||||
|
||||
// LetsEncryptStaging holds the staging directory url
|
||||
LetsEncryptStaging = "https://acme-staging-v02.api.letsencrypt.org/directory"
|
||||
|
||||
// ZeroSSLProduction holds the ZeroSSL directory url
|
||||
ZeroSSLProduction = "https://acme.zerossl.com/v2/DV90"
|
||||
|
||||
userAgentString = "eggsampler-acme/v3 Go-http-client/1.1"
|
||||
)
|
||||
|
||||
// NewClient creates a new acme client given a valid directory url.
|
||||
func NewClient(directoryURL string, options ...OptionFunc) (Client, error) {
|
||||
// Set a default http timeout of 60 seconds, this can be overridden
|
||||
// via an OptionFunc eg: acme.NewClient(url, WithHTTPTimeout(10 * time.Second))
|
||||
httpClient := &http.Client{
|
||||
Timeout: 60 * time.Second,
|
||||
}
|
||||
|
||||
acmeClient := Client{
|
||||
httpClient: httpClient,
|
||||
nonces: &nonceStack{},
|
||||
retryCount: 5,
|
||||
}
|
||||
|
||||
acmeClient.dir.URL = directoryURL
|
||||
|
||||
for _, opt := range options {
|
||||
if err := opt(&acmeClient); err != nil {
|
||||
return acmeClient, fmt.Errorf("acme: error setting option: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := acmeClient.get(directoryURL, &acmeClient.dir, http.StatusOK); err != nil {
|
||||
return acmeClient, err
|
||||
}
|
||||
|
||||
return acmeClient, nil
|
||||
}
|
||||
|
||||
// Directory is the object returned by the client connecting to a directory url.
|
||||
func (c Client) Directory() Directory {
|
||||
return c.dir
|
||||
}
|
||||
|
||||
// Helper function to get the poll interval and poll timeout, defaulting if 0
|
||||
func (c Client) getPollingDurations() (time.Duration, time.Duration) {
|
||||
pollInterval := c.PollInterval
|
||||
if pollInterval == 0 {
|
||||
pollInterval = 500 * time.Millisecond
|
||||
}
|
||||
pollTimeout := c.PollTimeout
|
||||
if pollTimeout == 0 {
|
||||
pollTimeout = 30 * time.Second
|
||||
}
|
||||
return pollInterval, pollTimeout
|
||||
}
|
||||
|
||||
// Helper function to have a central point for performing http requests. Stores
|
||||
// any returned nonces in the stack. The caller is responsible for closing the
|
||||
// body so they can read the response.
|
||||
func (c Client) do(req *http.Request, addNonce bool) (*http.Response, error) {
|
||||
// identifier for this client, as well as the default go user agent
|
||||
if c.userAgentSuffix != "" {
|
||||
req.Header.Set("User-Agent", userAgentString+" "+c.userAgentSuffix)
|
||||
} else {
|
||||
req.Header.Set("User-Agent", userAgentString)
|
||||
}
|
||||
|
||||
if c.acceptLanguage != "" {
|
||||
req.Header.Set("Accept-Language", c.acceptLanguage)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if addNonce {
|
||||
c.nonces.push(resp.Header.Get("Replay-Nonce"))
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Helper function to perform an HTTP get request and read the body. The caller
|
||||
// is responsible for closing the body so they can read the response.
|
||||
func (c Client) getRaw(url string, expectedStatus ...int) (*http.Response, []byte, error) {
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("acme: error creating request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := c.do(req, true)
|
||||
if err != nil {
|
||||
return resp, nil, fmt.Errorf("acme: error fetching response: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := checkError(resp, expectedStatus...); err != nil {
|
||||
return resp, nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return resp, body, fmt.Errorf("acme: error reading response body: %v", err)
|
||||
}
|
||||
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
// Helper function for performing a http get on an acme resource. The caller is
|
||||
// responsible for closing the body so they can read the response.
|
||||
func (c Client) get(url string, out interface{}, expectedStatus ...int) (*http.Response, error) {
|
||||
resp, body, err := c.getRaw(url, expectedStatus...)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if len(body) > 0 && out != nil {
|
||||
if err := json.Unmarshal(body, out); err != nil {
|
||||
return resp, fmt.Errorf("acme: error parsing response body: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (c Client) nonce() (string, error) {
|
||||
nonce := c.nonces.pop()
|
||||
if nonce != "" {
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
if c.dir.NewNonce == "" {
|
||||
return "", errors.New("acme: no new nonce url")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("HEAD", c.dir.NewNonce, nil)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("acme: error creating new nonce request: %v", err)
|
||||
}
|
||||
|
||||
resp, err := c.do(req, false)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("acme: error fetching new nonce: %v", err)
|
||||
}
|
||||
|
||||
nonce = resp.Header.Get("Replay-Nonce")
|
||||
return nonce, nil
|
||||
}
|
||||
|
||||
// Helper function to perform an HTTP post request and read the body. Will
|
||||
// attempt to retry if error is badNonce. The caller is responsible for closing
|
||||
// the body so they can read the response.
|
||||
func (c Client) postRaw(retryCount int, requestURL, kid string, privateKey crypto.Signer, payload interface{}, expectedStatus []int) (*http.Response, []byte, error) {
|
||||
nonce, err := c.nonce()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
data, err := jwsEncodeJSON(payload, privateKey, KeyID(kid), nonce, requestURL)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("acme: error encoding json payload: %v", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(http.MethodPost, requestURL, bytes.NewReader(data))
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("acme: error creating request: %v", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/jose+json")
|
||||
|
||||
resp, err := c.do(req, true)
|
||||
if err != nil {
|
||||
return resp, nil, fmt.Errorf("acme: error sending request: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if err := checkError(resp, expectedStatus...); err != nil {
|
||||
prob, ok := err.(Problem)
|
||||
if !ok {
|
||||
// don't retry for an error we don't know about
|
||||
return resp, nil, err
|
||||
}
|
||||
if retryCount >= c.retryCount {
|
||||
// don't attempt to retry if too many retries
|
||||
return resp, nil, err
|
||||
}
|
||||
if strings.HasSuffix(prob.Type, ":badNonce") {
|
||||
// only retry if error is badNonce
|
||||
return c.postRaw(retryCount+1, requestURL, kid, privateKey, payload, expectedStatus)
|
||||
}
|
||||
return resp, nil, err
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return resp, body, fmt.Errorf("acme: error reading response body: %v", err)
|
||||
}
|
||||
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
// Helper function for performing a http post to an acme resource. The caller is
|
||||
// responsible for closing the body so they can read the response.
|
||||
func (c Client) post(requestURL, keyID string, privateKey crypto.Signer, payload interface{}, out interface{}, expectedStatus ...int) (*http.Response, error) {
|
||||
resp, body, err := c.postRaw(0, requestURL, keyID, privateKey, payload, expectedStatus)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
if _, b := os.LookupEnv("ACME_DEBUG_POST"); b {
|
||||
fmt.Println()
|
||||
fmt.Println("========= " + requestURL)
|
||||
fmt.Println(string(body))
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
if len(body) > 0 && out != nil {
|
||||
if err := json.Unmarshal(body, out); err != nil {
|
||||
return resp, fmt.Errorf("acme: error parsing response: %v - %s", err, string(body))
|
||||
}
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
var regLink = regexp.MustCompile(`<(.+?)>;\s*rel="(.+?)"`)
|
||||
|
||||
// Fetches a http Link header from an http response and closes the body.
|
||||
func fetchLink(resp *http.Response, wantedLink string) string {
|
||||
if resp == nil {
|
||||
return ""
|
||||
}
|
||||
linkHeader := resp.Header["Link"]
|
||||
if len(linkHeader) == 0 {
|
||||
return ""
|
||||
}
|
||||
for _, l := range linkHeader {
|
||||
matches := regLink.FindAllStringSubmatch(l, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) != 3 {
|
||||
continue
|
||||
}
|
||||
if m[2] == wantedLink {
|
||||
return m[1]
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Fetch is a helper function to assist with POST-AS-GET requests
|
||||
func (c Client) Fetch(account Account, requestURL string, result interface{}, expectedStatus ...int) error {
|
||||
if len(expectedStatus) == 0 {
|
||||
expectedStatus = []int{http.StatusOK}
|
||||
}
|
||||
_, err := c.post(requestURL, account.URL, account.PrivateKey, "", result, expectedStatus...)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Fetches all http Link header from a http response
|
||||
func fetchLinks(resp *http.Response, wantedLink string) []string {
|
||||
if resp == nil {
|
||||
return nil
|
||||
}
|
||||
linkHeader := resp.Header["Link"]
|
||||
if len(linkHeader) == 0 {
|
||||
return nil
|
||||
}
|
||||
var links []string
|
||||
for _, l := range linkHeader {
|
||||
matches := regLink.FindAllStringSubmatch(l, -1)
|
||||
for _, m := range matches {
|
||||
if len(m) != 3 {
|
||||
continue
|
||||
}
|
||||
if m[2] == wantedLink {
|
||||
links = append(links, m[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
return links
|
||||
}
|
||||
131
vendor/github.com/eggsampler/acme/v3/ari.go
generated
vendored
Normal file
131
vendor/github.com/eggsampler/acme/v3/ari.go
generated
vendored
Normal file
@@ -0,0 +1,131 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
_ "crypto/sha1"
|
||||
_ "crypto/sha256"
|
||||
_ "crypto/sha512"
|
||||
"crypto/x509"
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// GetRenewalInfo returns the renewal information (if present and supported by
|
||||
// the ACME server), and a Retry-After time if indicated in the http response
|
||||
// header.
|
||||
func (c Client) GetRenewalInfo(cert *x509.Certificate) (RenewalInfo, error) {
|
||||
if c.dir.RenewalInfo == "" {
|
||||
return RenewalInfo{}, ErrRenewalInfoNotSupported
|
||||
}
|
||||
|
||||
certID, err := GenerateARICertID(cert)
|
||||
if err != nil {
|
||||
return RenewalInfo{}, fmt.Errorf("acme: error generating certificate id: %v", err)
|
||||
}
|
||||
|
||||
renewalURL := c.dir.RenewalInfo
|
||||
if !strings.HasSuffix(renewalURL, "/") {
|
||||
renewalURL += "/"
|
||||
}
|
||||
renewalURL += certID
|
||||
var ri RenewalInfo
|
||||
|
||||
resp, err := c.get(renewalURL, &ri, http.StatusOK)
|
||||
if err != nil {
|
||||
return ri, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
ri.RetryAfter, err = parseRetryAfter(resp.Header.Get("Retry-After"))
|
||||
return ri, err
|
||||
}
|
||||
|
||||
// GenerateARICertID constructs a certificate identifier as described in
|
||||
// draft-ietf-acme-ari-03, section 4.1.
|
||||
func GenerateARICertID(cert *x509.Certificate) (string, error) {
|
||||
if cert == nil {
|
||||
return "", fmt.Errorf("certificate not found")
|
||||
}
|
||||
|
||||
derBytes, err := asn1.Marshal(cert.SerialNumber)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if len(derBytes) < 3 {
|
||||
return "", fmt.Errorf("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). The result is base64url
|
||||
// encoded without padding.
|
||||
serial := base64.RawURLEncoding.EncodeToString(derBytes[2:])
|
||||
|
||||
// Convert the Authority Key Identifier to base64url encoding without
|
||||
// padding.
|
||||
aki := base64.RawURLEncoding.EncodeToString(cert.AuthorityKeyId)
|
||||
|
||||
// Construct the final identifier by concatenating AKI and Serial Number.
|
||||
return fmt.Sprintf("%s.%s", aki, serial), nil
|
||||
}
|
||||
|
||||
func (r RenewalInfo) 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.
|
||||
window := end.Sub(start)
|
||||
randomDuration := time.Duration(rand.Int63n(int64(window)))
|
||||
randomTime := start.Add(randomDuration)
|
||||
|
||||
// If the selected time is in the past, attempt renewal immediately.
|
||||
if randomTime.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(randomTime) || willingToSleepUntil.Equal(randomTime) {
|
||||
return &randomTime
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// timeNow and implementations support testing
|
||||
type timeNow interface {
|
||||
Now() time.Time
|
||||
}
|
||||
|
||||
type currentTimeNow struct{}
|
||||
|
||||
func (currentTimeNow) Now() time.Time {
|
||||
return time.Now()
|
||||
}
|
||||
|
||||
var systemTime timeNow = currentTimeNow{}
|
||||
|
||||
func parseRetryAfter(ra string) (time.Time, error) {
|
||||
retryAfterString := strings.TrimSpace(ra)
|
||||
if len(retryAfterString) == 0 {
|
||||
return time.Time{}, nil
|
||||
}
|
||||
|
||||
if retryAfterTime, err := time.Parse(time.RFC1123, retryAfterString); err == nil {
|
||||
return retryAfterTime, nil
|
||||
}
|
||||
|
||||
if retryAfterInt, err := strconv.Atoi(retryAfterString); err == nil {
|
||||
return systemTime.Now().Add(time.Second * time.Duration(retryAfterInt)), nil
|
||||
}
|
||||
|
||||
return time.Time{}, fmt.Errorf("invalid time format: %s", retryAfterString)
|
||||
}
|
||||
43
vendor/github.com/eggsampler/acme/v3/authorization.go
generated
vendored
Normal file
43
vendor/github.com/eggsampler/acme/v3/authorization.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
package acme
|
||||
|
||||
import "net/http"
|
||||
|
||||
// FetchAuthorization fetches an authorization from an authorization url provided in an order.
|
||||
func (c Client) FetchAuthorization(account Account, authURL string) (Authorization, error) {
|
||||
authResp := Authorization{}
|
||||
_, err := c.post(authURL, account.URL, account.PrivateKey, "", &authResp, http.StatusOK)
|
||||
if err != nil {
|
||||
return authResp, err
|
||||
}
|
||||
|
||||
for i := 0; i < len(authResp.Challenges); i++ {
|
||||
if authResp.Challenges[i].KeyAuthorization == "" {
|
||||
authResp.Challenges[i].KeyAuthorization = authResp.Challenges[i].Token + "." + account.Thumbprint
|
||||
}
|
||||
}
|
||||
|
||||
authResp.ChallengeMap = map[string]Challenge{}
|
||||
authResp.ChallengeTypes = []string{}
|
||||
for _, c := range authResp.Challenges {
|
||||
authResp.ChallengeMap[c.Type] = c
|
||||
authResp.ChallengeTypes = append(authResp.ChallengeTypes, c.Type)
|
||||
}
|
||||
|
||||
authResp.URL = authURL
|
||||
|
||||
return authResp, nil
|
||||
}
|
||||
|
||||
// DeactivateAuthorization deactivate a provided authorization url from an order.
|
||||
func (c Client) DeactivateAuthorization(account Account, authURL string) (Authorization, error) {
|
||||
deactivateReq := struct {
|
||||
Status string `json:"status"`
|
||||
}{
|
||||
Status: "deactivated",
|
||||
}
|
||||
deactivateResp := Authorization{}
|
||||
|
||||
_, err := c.post(authURL, account.URL, account.PrivateKey, deactivateReq, &deactivateResp, http.StatusOK)
|
||||
|
||||
return deactivateResp, err
|
||||
}
|
||||
435
vendor/github.com/eggsampler/acme/v3/autocert.go
generated
vendored
Normal file
435
vendor/github.com/eggsampler/acme/v3/autocert.go
generated
vendored
Normal file
@@ -0,0 +1,435 @@
|
||||
package acme
|
||||
|
||||
// Similar to golang.org/x/crypto/acme/autocert
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"crypto/elliptic"
|
||||
"crypto/rand"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// HostCheck function prototype to implement for checking hosts against before issuing certificates
|
||||
type HostCheck func(host string) error
|
||||
|
||||
// WhitelistHosts implements a simple whitelist HostCheck
|
||||
func WhitelistHosts(hosts ...string) HostCheck {
|
||||
m := map[string]bool{}
|
||||
for _, v := range hosts {
|
||||
m[v] = true
|
||||
}
|
||||
|
||||
return func(host string) error {
|
||||
if !m[host] {
|
||||
return errors.New("autocert: host not whitelisted")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// AutoCert is a stateful certificate manager for issuing certificates on connecting hosts
|
||||
type AutoCert struct {
|
||||
// Acme directory Url
|
||||
// If nil, uses `LetsEncryptStaging`
|
||||
DirectoryURL string
|
||||
|
||||
// Options contains the options used for creating the acme client
|
||||
Options []OptionFunc
|
||||
|
||||
// A function to check whether a host is allowed or not
|
||||
// If nil, all hosts allowed
|
||||
// Use `WhitelistHosts(hosts ...string)` for a simple white list of hostnames
|
||||
HostCheck HostCheck
|
||||
|
||||
// Cache dir to store account data and certificates
|
||||
// If nil, does not write cache data to file
|
||||
CacheDir string
|
||||
|
||||
// When using a staging environment, include a root certificate for verification purposes
|
||||
RootCert string
|
||||
|
||||
// Called before updating challenges
|
||||
PreUpdateChallengeHook func(Account, Challenge)
|
||||
|
||||
// Mapping of token -> keyauth
|
||||
// Protected by a mutex, but not rwmutex because tokens are deleted once read
|
||||
tokensLock sync.RWMutex
|
||||
tokens map[string][]byte
|
||||
|
||||
// Mapping of cache key -> value
|
||||
cacheLock sync.Mutex
|
||||
cache map[string][]byte
|
||||
|
||||
// read lock around getting existing certs
|
||||
// write lock around issuing new certificate
|
||||
certLock sync.RWMutex
|
||||
|
||||
client Client
|
||||
}
|
||||
|
||||
// HTTPHandler Wraps a handler and provides serving of http-01 challenge tokens from /.well-known/acme-challenge/
|
||||
// If handler is nil, will redirect all traffic otherwise to https
|
||||
func (m *AutoCert) HTTPHandler(handler http.Handler) http.Handler {
|
||||
if handler == nil {
|
||||
handler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, "https://"+r.Host+r.URL.RequestURI(), http.StatusMovedPermanently)
|
||||
})
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if !strings.HasPrefix(r.URL.Path, "/.well-known/acme-challenge/") {
|
||||
handler.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
if err := m.checkHost(r.Host); err != nil {
|
||||
http.Error(w, err.Error(), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
token := path.Base(r.URL.Path)
|
||||
m.tokensLock.RLock()
|
||||
defer m.tokensLock.RUnlock()
|
||||
keyAuth := m.tokens[token]
|
||||
if len(keyAuth) == 0 {
|
||||
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
_, _ = w.Write(keyAuth)
|
||||
})
|
||||
}
|
||||
|
||||
// GetCertificate implements a tls.Config.GetCertificate hook
|
||||
func (m *AutoCert) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
name := strings.TrimSuffix(hello.ServerName, ".")
|
||||
|
||||
if name == "" {
|
||||
return nil, errors.New("autocert: missing server name")
|
||||
}
|
||||
if !strings.Contains(strings.Trim(name, "."), ".") {
|
||||
return nil, errors.New("autocert: server name component count invalid")
|
||||
}
|
||||
if strings.ContainsAny(name, `/\`) {
|
||||
return nil, errors.New("autocert: server name contains invalid character")
|
||||
}
|
||||
|
||||
// check the hostname is allowed
|
||||
if err := m.checkHost(name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// check if there's an existing cert
|
||||
m.certLock.RLock()
|
||||
existingCert, _ := m.getExistingCert(name)
|
||||
m.certLock.RUnlock()
|
||||
if existingCert != nil {
|
||||
return existingCert, nil
|
||||
}
|
||||
|
||||
// if not, attempt to issue a new cert
|
||||
m.certLock.Lock()
|
||||
defer m.certLock.Unlock()
|
||||
return m.issueCert(name)
|
||||
}
|
||||
|
||||
func (m *AutoCert) getDirectoryURL() string {
|
||||
if m.DirectoryURL != "" {
|
||||
return m.DirectoryURL
|
||||
}
|
||||
|
||||
return LetsEncryptStaging
|
||||
}
|
||||
|
||||
func (m *AutoCert) getCache(keys ...string) []byte {
|
||||
key := strings.Join(keys, "-")
|
||||
|
||||
m.cacheLock.Lock()
|
||||
defer m.cacheLock.Unlock()
|
||||
|
||||
b := m.cache[key]
|
||||
if len(b) > 0 {
|
||||
return b
|
||||
}
|
||||
|
||||
if m.CacheDir == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
b, _ = ioutil.ReadFile(path.Join(m.CacheDir, key))
|
||||
if len(b) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if m.cache == nil {
|
||||
m.cache = map[string][]byte{}
|
||||
}
|
||||
m.cache[key] = b
|
||||
return b
|
||||
}
|
||||
|
||||
func (m *AutoCert) putCache(data []byte, keys ...string) context.Context {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
key := strings.Join(keys, "-")
|
||||
|
||||
m.cacheLock.Lock()
|
||||
defer m.cacheLock.Unlock()
|
||||
|
||||
if m.cache == nil {
|
||||
m.cache = map[string][]byte{}
|
||||
}
|
||||
m.cache[key] = data
|
||||
|
||||
if m.CacheDir == "" {
|
||||
cancel()
|
||||
return ctx
|
||||
}
|
||||
|
||||
go func() {
|
||||
_ = ioutil.WriteFile(path.Join(m.CacheDir, key), data, 0700)
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return ctx
|
||||
}
|
||||
|
||||
func (m *AutoCert) checkHost(name string) error {
|
||||
if m.HostCheck == nil {
|
||||
return nil
|
||||
}
|
||||
return m.HostCheck(name)
|
||||
}
|
||||
|
||||
func (m *AutoCert) getExistingCert(name string) (*tls.Certificate, error) {
|
||||
// check for a stored cert
|
||||
certData := m.getCache("cert", name)
|
||||
if len(certData) == 0 {
|
||||
return nil, errors.New("autocert: no existing certificate")
|
||||
}
|
||||
|
||||
privBlock, pubData := pem.Decode(certData)
|
||||
if len(pubData) == 0 {
|
||||
return nil, errors.New("autocert: no public key data (cert/issuer)")
|
||||
}
|
||||
|
||||
// decode pub chain
|
||||
var pubDER [][]byte
|
||||
var pub []byte
|
||||
for len(pubData) > 0 {
|
||||
var b *pem.Block
|
||||
b, pubData = pem.Decode(pubData)
|
||||
if b == nil {
|
||||
break
|
||||
}
|
||||
pubDER = append(pubDER, b.Bytes)
|
||||
pub = append(pub, b.Bytes...)
|
||||
}
|
||||
if len(pubData) > 0 {
|
||||
return nil, errors.New("autocert: leftover data in file - possibly corrupt")
|
||||
}
|
||||
|
||||
certs, err := x509.ParseCertificates(pub)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: bad certificate: %v", err)
|
||||
}
|
||||
|
||||
leaf := certs[0]
|
||||
|
||||
// add any intermediate certs if present
|
||||
var intermediates *x509.CertPool
|
||||
if len(certs) > 1 {
|
||||
intermediates = x509.NewCertPool()
|
||||
for i := 1; i < len(certs); i++ {
|
||||
intermediates.AddCert(certs[i])
|
||||
}
|
||||
}
|
||||
|
||||
// add a root certificate if present
|
||||
var roots *x509.CertPool
|
||||
if m.RootCert != "" {
|
||||
block, rest := pem.Decode([]byte(m.RootCert))
|
||||
for block != nil {
|
||||
rootCert, err := x509.ParseCertificate(block.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("autocert: error parsing root certificate")
|
||||
}
|
||||
if roots == nil {
|
||||
roots = x509.NewCertPool()
|
||||
}
|
||||
roots.AddCert(rootCert)
|
||||
block, rest = pem.Decode(rest)
|
||||
}
|
||||
}
|
||||
|
||||
opts := x509.VerifyOptions{
|
||||
DNSName: name,
|
||||
Intermediates: intermediates,
|
||||
Roots: roots,
|
||||
}
|
||||
|
||||
if _, err := leaf.Verify(opts); err != nil {
|
||||
return nil, fmt.Errorf("autocert: unable to verify: %v", err)
|
||||
}
|
||||
|
||||
privKey, err := x509.ParseECPrivateKey(privBlock.Bytes)
|
||||
if err != nil {
|
||||
return nil, errors.New("autocert: invalid private key")
|
||||
}
|
||||
|
||||
return &tls.Certificate{
|
||||
Certificate: pubDER,
|
||||
PrivateKey: privKey,
|
||||
Leaf: leaf,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (m *AutoCert) issueCert(domainName string) (*tls.Certificate, error) {
|
||||
// attempt to load an existing account key
|
||||
var privKey *ecdsa.PrivateKey
|
||||
if keyData := m.getCache("account"); len(keyData) > 0 {
|
||||
block, _ := pem.Decode(keyData)
|
||||
x509Encoded := block.Bytes
|
||||
privKey, _ = x509.ParseECPrivateKey(x509Encoded)
|
||||
}
|
||||
|
||||
// otherwise generate a new one
|
||||
if privKey == nil {
|
||||
var err error
|
||||
privKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error generating new account key: %v", err)
|
||||
}
|
||||
|
||||
x509Encoded, _ := x509.MarshalECPrivateKey(privKey)
|
||||
pemEncoded := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: x509Encoded})
|
||||
|
||||
m.putCache(pemEncoded, "account")
|
||||
}
|
||||
|
||||
// create a new client if one doesn't exist
|
||||
if m.client.Directory().URL == "" {
|
||||
var err error
|
||||
m.client, err = NewClient(m.getDirectoryURL(), m.Options...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// create/fetch acme account
|
||||
account, err := m.client.NewAccount(privKey, false, true)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error creating/fetching account: %v", err)
|
||||
}
|
||||
|
||||
// start a new order process
|
||||
order, err := m.client.NewOrderDomains(account, domainName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error creating new order for domain %s: %v", domainName, err)
|
||||
}
|
||||
|
||||
// loop through each of the provided authorization Urls
|
||||
for _, authURL := range order.Authorizations {
|
||||
auth, err := m.client.FetchAuthorization(account, authURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error fetching authorization Url %q: %v", authURL, err)
|
||||
}
|
||||
|
||||
if auth.Status == "valid" {
|
||||
continue
|
||||
}
|
||||
|
||||
chal, ok := auth.ChallengeMap[ChallengeTypeHTTP01]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("autocert: unable to find http-01 challenge for auth %s, Url: %s", auth.Identifier.Value, authURL)
|
||||
}
|
||||
|
||||
m.tokensLock.Lock()
|
||||
if m.tokens == nil {
|
||||
m.tokens = map[string][]byte{}
|
||||
}
|
||||
m.tokens[chal.Token] = []byte(chal.KeyAuthorization)
|
||||
m.tokensLock.Unlock()
|
||||
|
||||
if m.PreUpdateChallengeHook != nil {
|
||||
m.PreUpdateChallengeHook(account, chal)
|
||||
}
|
||||
|
||||
chal, err = m.client.UpdateChallenge(account, chal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error updating authorization %s challenge (Url: %s) : %v", auth.Identifier.Value, authURL, err)
|
||||
}
|
||||
|
||||
m.tokensLock.Lock()
|
||||
delete(m.tokens, chal.Token)
|
||||
m.tokensLock.Unlock()
|
||||
}
|
||||
|
||||
// generate private key for cert
|
||||
certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error generating certificate key for %s: %v", domainName, err)
|
||||
}
|
||||
certKeyEnc, err := x509.MarshalECPrivateKey(certKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error encoding certificate key for %s: %v", domainName, err)
|
||||
}
|
||||
certKeyPem := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: certKeyEnc,
|
||||
})
|
||||
|
||||
// create the new csr template
|
||||
tpl := &x509.CertificateRequest{
|
||||
SignatureAlgorithm: x509.ECDSAWithSHA256,
|
||||
PublicKeyAlgorithm: x509.ECDSA,
|
||||
PublicKey: certKey.Public(),
|
||||
Subject: pkix.Name{CommonName: domainName},
|
||||
DNSNames: []string{domainName},
|
||||
}
|
||||
csrDer, err := x509.CreateCertificateRequest(rand.Reader, tpl, certKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error creating certificate request for %s: %v", domainName, err)
|
||||
}
|
||||
csr, err := x509.ParseCertificateRequest(csrDer)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error parsing certificate request for %s: %v", domainName, err)
|
||||
}
|
||||
|
||||
// finalize the order with the acme server given a csr
|
||||
order, err = m.client.FinalizeOrder(account, order, csr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error finalizing order for %s: %v", domainName, err)
|
||||
}
|
||||
|
||||
// fetch the certificate chain from the finalized order provided by the acme server
|
||||
certs, err := m.client.FetchCertificates(account, order.Certificate)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("autocert: error fetching order certificates for %s: %v", domainName, err)
|
||||
}
|
||||
|
||||
certPem := certKeyPem
|
||||
// var certDer [][]byte
|
||||
for _, c := range certs {
|
||||
b := pem.EncodeToMemory(&pem.Block{
|
||||
Type: "CERTIFICATE",
|
||||
Bytes: c.Raw,
|
||||
})
|
||||
certPem = append(certPem, b...)
|
||||
// certDer = append(certDer, c.Raw)
|
||||
}
|
||||
m.putCache(certPem, "cert", domainName)
|
||||
|
||||
return m.getExistingCert(domainName)
|
||||
}
|
||||
106
vendor/github.com/eggsampler/acme/v3/certificate.go
generated
vendored
Normal file
106
vendor/github.com/eggsampler/acme/v3/certificate.go
generated
vendored
Normal file
@@ -0,0 +1,106 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func (c Client) decodeCertificateChain(body []byte, resp *http.Response, account Account) ([]*x509.Certificate, error) {
|
||||
var certs []*x509.Certificate
|
||||
for {
|
||||
var p *pem.Block
|
||||
p, body = pem.Decode(body)
|
||||
if p == nil {
|
||||
break
|
||||
}
|
||||
cert, err := x509.ParseCertificate(p.Bytes)
|
||||
if err != nil {
|
||||
return certs, fmt.Errorf("acme: error parsing certificate: %v", err)
|
||||
}
|
||||
certs = append(certs, cert)
|
||||
}
|
||||
|
||||
up := fetchLink(resp, "up")
|
||||
if up != "" {
|
||||
upCerts, err := c.FetchCertificates(account, up)
|
||||
if err != nil {
|
||||
return certs, fmt.Errorf("acme: error fetching up cert: %v", err)
|
||||
}
|
||||
if len(upCerts) != 0 {
|
||||
certs = append(certs, upCerts...)
|
||||
}
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
}
|
||||
|
||||
// FetchCertificates downloads a certificate chain from a url given in an order certificate.
|
||||
func (c Client) FetchCertificates(account Account, certificateURL string) ([]*x509.Certificate, error) {
|
||||
resp, body, err := c.postRaw(0, certificateURL, account.URL, account.PrivateKey, "", []int{http.StatusOK})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c.decodeCertificateChain(body, resp, account)
|
||||
}
|
||||
|
||||
// FetchAllCertificates downloads a certificate chain from a url given in an order certificate, as well as any alternate certificates if provided.
|
||||
// Returns a mapping of certificate urls to the certificate chain.
|
||||
func (c Client) FetchAllCertificates(account Account, certificateURL string) (map[string][]*x509.Certificate, error) {
|
||||
resp, body, err := c.postRaw(0, certificateURL, account.URL, account.PrivateKey, "", []int{http.StatusOK})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certChain, err := c.decodeCertificateChain(body, resp, account)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
certs := map[string][]*x509.Certificate{
|
||||
certificateURL: certChain,
|
||||
}
|
||||
|
||||
alternates := fetchLinks(resp, "alternate")
|
||||
|
||||
for _, altURL := range alternates {
|
||||
altResp, altBody, err := c.postRaw(0, altURL, account.URL, account.PrivateKey, "", []int{http.StatusOK})
|
||||
if err != nil {
|
||||
return certs, fmt.Errorf("acme: error fetching alt cert chain at %q - %v", altURL, err)
|
||||
}
|
||||
altCertChain, err := c.decodeCertificateChain(altBody, altResp, account)
|
||||
if err != nil {
|
||||
return certs, fmt.Errorf("acme: error decoding alt cert chain at %q - %v", altURL, err)
|
||||
}
|
||||
certs[altURL] = altCertChain
|
||||
}
|
||||
|
||||
return certs, nil
|
||||
|
||||
}
|
||||
|
||||
// RevokeCertificate revokes a given certificate given the certificate key or account key, and a reason.
|
||||
func (c Client) RevokeCertificate(account Account, cert *x509.Certificate, key crypto.Signer, reason int) error {
|
||||
revokeReq := struct {
|
||||
Certificate string `json:"certificate"`
|
||||
Reason int `json:"reason"`
|
||||
}{
|
||||
Certificate: base64.RawURLEncoding.EncodeToString(cert.Raw),
|
||||
Reason: reason,
|
||||
}
|
||||
|
||||
kid := ""
|
||||
if key == account.PrivateKey {
|
||||
kid = account.URL
|
||||
}
|
||||
|
||||
if _, err := c.post(c.dir.RevokeCert, kid, key, revokeReq, nil, http.StatusOK); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
102
vendor/github.com/eggsampler/acme/v3/challenge.go
generated
vendored
Normal file
102
vendor/github.com/eggsampler/acme/v3/challenge.go
generated
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// EncodeDNS01KeyAuthorization encodes a key authorization and provides a value to be put in the TXT record for the _acme-challenge DNS entry.
|
||||
func EncodeDNS01KeyAuthorization(keyAuth string) string {
|
||||
h := sha256.Sum256([]byte(keyAuth))
|
||||
return base64.RawURLEncoding.EncodeToString(h[:])
|
||||
}
|
||||
|
||||
// Helper function to determine whether a challenge is "finished" by its status.
|
||||
func checkUpdatedChallengeStatus(challenge Challenge) (bool, error) {
|
||||
switch challenge.Status {
|
||||
case "pending":
|
||||
// Challenge objects are created in the "pending" state.
|
||||
// TODO: https://github.com/letsencrypt/boulder/issues/3346
|
||||
// return true, errors.New("acme: unexpected 'pending' challenge state")
|
||||
return false, nil
|
||||
|
||||
case "processing":
|
||||
// They transition to the "processing" state when the client responds to the
|
||||
// challenge and the server begins attempting to validate that the client has completed the challenge.
|
||||
return false, nil
|
||||
|
||||
case "valid":
|
||||
// If validation is successful, the challenge moves to the "valid" state
|
||||
return true, nil
|
||||
|
||||
case "invalid":
|
||||
// if there is an error, the challenge moves to the "invalid" state.
|
||||
if challenge.Error.Type != "" {
|
||||
return true, challenge.Error
|
||||
}
|
||||
return true, errors.New("acme: challenge is invalid, no error provided")
|
||||
|
||||
default:
|
||||
return true, fmt.Errorf("acme: unknown challenge status: %s", challenge.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// UpdateChallenge responds to a challenge to indicate to the server to complete the challenge.
|
||||
func (c Client) UpdateChallenge(account Account, challenge Challenge) (Challenge, error) {
|
||||
resp, err := c.post(challenge.URL, account.URL, account.PrivateKey, struct{}{}, &challenge, http.StatusOK)
|
||||
if err != nil {
|
||||
return challenge, err
|
||||
}
|
||||
|
||||
if loc := resp.Header.Get("Location"); loc != "" {
|
||||
challenge.URL = loc
|
||||
}
|
||||
challenge.AuthorizationURL = fetchLink(resp, "up")
|
||||
|
||||
if finished, err := checkUpdatedChallengeStatus(challenge); finished {
|
||||
return challenge, err
|
||||
}
|
||||
|
||||
pollInterval, pollTimeout := c.getPollingDurations()
|
||||
end := time.Now().Add(pollTimeout)
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
return challenge, errors.New("acme: challenge update timeout")
|
||||
}
|
||||
time.Sleep(pollInterval)
|
||||
|
||||
resp, err := c.post(challenge.URL, account.URL, account.PrivateKey, "", &challenge, http.StatusOK)
|
||||
if err != nil {
|
||||
// i don't think it's worth exiting the loop on this error
|
||||
// it could just be connectivity issue that's resolved before the timeout duration
|
||||
continue
|
||||
}
|
||||
|
||||
if loc := resp.Header.Get("Location"); loc != "" {
|
||||
challenge.URL = loc
|
||||
}
|
||||
challenge.AuthorizationURL = fetchLink(resp, "up")
|
||||
|
||||
if finished, err := checkUpdatedChallengeStatus(challenge); finished {
|
||||
return challenge, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// FetchChallenge fetches an existing challenge from the given url.
|
||||
func (c Client) FetchChallenge(account Account, challengeURL string) (Challenge, error) {
|
||||
challenge := Challenge{}
|
||||
resp, err := c.post(challengeURL, account.URL, account.PrivateKey, "", &challenge, http.StatusOK)
|
||||
if err != nil {
|
||||
return challenge, err
|
||||
}
|
||||
|
||||
challenge.URL = resp.Header.Get("Location")
|
||||
challenge.AuthorizationURL = fetchLink(resp, "up")
|
||||
|
||||
return challenge, nil
|
||||
}
|
||||
5
vendor/github.com/eggsampler/acme/v3/docker-compose.boulder-temp.yml
generated
vendored
Normal file
5
vendor/github.com/eggsampler/acme/v3/docker-compose.boulder-temp.yml
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
version: '3'
|
||||
services:
|
||||
boulder:
|
||||
ports:
|
||||
- "8055:8055"
|
||||
257
vendor/github.com/eggsampler/acme/v3/jws.go
generated
vendored
Normal file
257
vendor/github.com/eggsampler/acme/v3/jws.go
generated
vendored
Normal file
@@ -0,0 +1,257 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/ecdsa"
|
||||
"crypto/hmac"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"crypto/sha256"
|
||||
_ "crypto/sha512" // need for EC keys
|
||||
"encoding/asn1"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// KeyID is the account key identity provided by a CA during registration.
|
||||
type KeyID string
|
||||
|
||||
// noKeyID indicates that jwsEncodeJSON should compute and use JWK instead of a KID.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noKeyID = KeyID("")
|
||||
|
||||
// noPayload indicates jwsEncodeJSON will encode zero-length octet string
|
||||
// in a JWS request. This is called POST-as-GET in RFC 8555 and is used to make
|
||||
// authenticated GET requests via POSTing with an empty payload.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-6.3 for more details.
|
||||
const noPayload = ""
|
||||
|
||||
// noNonce indicates that the nonce should be omitted from the protected header.
|
||||
// See jwsEncodeJSON for details.
|
||||
const noNonce = ""
|
||||
|
||||
// jsonWebSignature can be easily serialized into a JWS following
|
||||
// https://tools.ietf.org/html/rfc7515#section-3.2.
|
||||
type jsonWebSignature struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}
|
||||
|
||||
// jwsEncodeJSON signs claimset using provided key and a nonce.
|
||||
// The result is serialized in JSON format containing either kid or jwk
|
||||
// fields based on the provided KeyID value.
|
||||
//
|
||||
// The claimset is marshalled using json.Marshal unless it is a string.
|
||||
// In which case it is inserted directly into the message.
|
||||
//
|
||||
// If kid is non-empty, its quoted value is inserted in the protected header
|
||||
// as "kid" field value. Otherwise, JWK is computed using jwkEncode and inserted
|
||||
// as "jwk" field value. The "jwk" and "kid" fields are mutually exclusive.
|
||||
//
|
||||
// If nonce is non-empty, its quoted value is inserted in the protected header.
|
||||
//
|
||||
// See https://tools.ietf.org/html/rfc7515#section-7.
|
||||
func jwsEncodeJSON(claimset interface{}, key crypto.Signer, kid KeyID, nonce, url string) ([]byte, error) {
|
||||
if key == nil {
|
||||
return nil, errors.New("nil key")
|
||||
}
|
||||
alg, sha := jwsHasher(key.Public())
|
||||
if alg == "" || !sha.Available() {
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
headers := struct {
|
||||
Alg string `json:"alg"`
|
||||
KID string `json:"kid,omitempty"`
|
||||
JWK json.RawMessage `json:"jwk,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
URL string `json:"url"`
|
||||
}{
|
||||
Alg: alg,
|
||||
Nonce: nonce,
|
||||
URL: url,
|
||||
}
|
||||
switch kid {
|
||||
case noKeyID:
|
||||
jwk, err := jwkEncode(key.Public())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers.JWK = json.RawMessage(jwk)
|
||||
default:
|
||||
headers.KID = string(kid)
|
||||
}
|
||||
phJSON, err := json.Marshal(headers)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
phead := base64.RawURLEncoding.EncodeToString([]byte(phJSON))
|
||||
var payload string
|
||||
if val, ok := claimset.(string); ok {
|
||||
payload = val
|
||||
} else {
|
||||
cs, err := json.Marshal(claimset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
payload = base64.RawURLEncoding.EncodeToString(cs)
|
||||
}
|
||||
hash := sha.New()
|
||||
hash.Write([]byte(phead + "." + payload))
|
||||
sig, err := jwsSign(key, sha, hash.Sum(nil))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
enc := jsonWebSignature{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(sig),
|
||||
}
|
||||
return json.Marshal(&enc)
|
||||
}
|
||||
|
||||
// jwsWithMAC creates and signs a JWS using the given key and the HS256
|
||||
// algorithm. kid and url are included in the protected header. rawPayload
|
||||
// should not be base64-URL-encoded.
|
||||
func jwsWithMAC(key []byte, kid, url string, rawPayload []byte) (*jsonWebSignature, error) {
|
||||
if len(key) == 0 {
|
||||
return nil, errors.New("acme: cannot sign JWS with an empty MAC key")
|
||||
}
|
||||
header := struct {
|
||||
Algorithm string `json:"alg"`
|
||||
KID string `json:"kid"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}{
|
||||
// Only HMAC-SHA256 is supported.
|
||||
Algorithm: "HS256",
|
||||
KID: kid,
|
||||
URL: url,
|
||||
}
|
||||
rawProtected, err := json.Marshal(header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protected := base64.RawURLEncoding.EncodeToString(rawProtected)
|
||||
payload := base64.RawURLEncoding.EncodeToString(rawPayload)
|
||||
|
||||
h := hmac.New(sha256.New, key)
|
||||
if _, err := h.Write([]byte(protected + "." + payload)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mac := h.Sum(nil)
|
||||
|
||||
return &jsonWebSignature{
|
||||
Protected: protected,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(mac),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// jwkEncode encodes public part of an RSA or ECDSA key into a JWK.
|
||||
// The result is also suitable for creating a JWK thumbprint.
|
||||
// https://tools.ietf.org/html/rfc7517
|
||||
func jwkEncode(pub crypto.PublicKey) (string, error) {
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.3.1
|
||||
n := pub.N
|
||||
e := big.NewInt(int64(pub.E))
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`,
|
||||
base64.RawURLEncoding.EncodeToString(e.Bytes()),
|
||||
base64.RawURLEncoding.EncodeToString(n.Bytes()),
|
||||
), nil
|
||||
case *ecdsa.PublicKey:
|
||||
// https://tools.ietf.org/html/rfc7518#section-6.2.1
|
||||
p := pub.Curve.Params()
|
||||
n := p.BitSize / 8
|
||||
if p.BitSize%8 != 0 {
|
||||
n++
|
||||
}
|
||||
x := pub.X.Bytes()
|
||||
if n > len(x) {
|
||||
x = append(make([]byte, n-len(x)), x...)
|
||||
}
|
||||
y := pub.Y.Bytes()
|
||||
if n > len(y) {
|
||||
y = append(make([]byte, n-len(y)), y...)
|
||||
}
|
||||
// Field order is important.
|
||||
// See https://tools.ietf.org/html/rfc7638#section-3.3 for details.
|
||||
return fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`,
|
||||
p.Name,
|
||||
base64.RawURLEncoding.EncodeToString(x),
|
||||
base64.RawURLEncoding.EncodeToString(y),
|
||||
), nil
|
||||
}
|
||||
return "", ErrUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsSign signs the digest using the given key.
|
||||
// The hash is unused for ECDSA keys.
|
||||
func jwsSign(key crypto.Signer, hash crypto.Hash, digest []byte) ([]byte, error) {
|
||||
switch pub := key.Public().(type) {
|
||||
case *rsa.PublicKey:
|
||||
return key.Sign(rand.Reader, digest, hash)
|
||||
case *ecdsa.PublicKey:
|
||||
sigASN1, err := key.Sign(rand.Reader, digest, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rs struct{ R, S *big.Int }
|
||||
if _, err := asn1.Unmarshal(sigASN1, &rs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rb, sb := rs.R.Bytes(), rs.S.Bytes()
|
||||
size := pub.Params().BitSize / 8
|
||||
if size%8 > 0 {
|
||||
size++
|
||||
}
|
||||
sig := make([]byte, size*2)
|
||||
copy(sig[size-len(rb):], rb)
|
||||
copy(sig[size*2-len(sb):], sb)
|
||||
return sig, nil
|
||||
}
|
||||
return nil, ErrUnsupportedKey
|
||||
}
|
||||
|
||||
// jwsHasher indicates suitable JWS algorithm name and a hash function
|
||||
// to use for signing a digest with the provided key.
|
||||
// It returns ("", 0) if the key is not supported.
|
||||
func jwsHasher(pub crypto.PublicKey) (string, crypto.Hash) {
|
||||
switch pub := pub.(type) {
|
||||
case *rsa.PublicKey:
|
||||
return "RS256", crypto.SHA256
|
||||
case *ecdsa.PublicKey:
|
||||
switch pub.Params().Name {
|
||||
case "P-256":
|
||||
return "ES256", crypto.SHA256
|
||||
case "P-384":
|
||||
return "ES384", crypto.SHA384
|
||||
case "P-521":
|
||||
return "ES512", crypto.SHA512
|
||||
}
|
||||
}
|
||||
return "", 0
|
||||
}
|
||||
|
||||
// JWKThumbprint creates a JWK thumbprint out of pub
|
||||
// as specified in https://tools.ietf.org/html/rfc7638.
|
||||
func JWKThumbprint(pub crypto.PublicKey) (string, error) {
|
||||
jwk, err := jwkEncode(pub)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b := sha256.Sum256([]byte(jwk))
|
||||
return base64.RawURLEncoding.EncodeToString(b[:]), nil
|
||||
}
|
||||
45
vendor/github.com/eggsampler/acme/v3/nonce.go
generated
vendored
Normal file
45
vendor/github.com/eggsampler/acme/v3/nonce.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Simple thread-safe stack impl
|
||||
type nonceStack struct {
|
||||
lock sync.Mutex
|
||||
stack []string
|
||||
}
|
||||
|
||||
// Pushes a nonce to the stack.
|
||||
// Doesn't push empty nonces, or if there's more than 100 nonces on the stack
|
||||
func (ns *nonceStack) push(v string) {
|
||||
if v == "" {
|
||||
return
|
||||
}
|
||||
|
||||
ns.lock.Lock()
|
||||
defer ns.lock.Unlock()
|
||||
|
||||
if len(ns.stack) > 100 {
|
||||
return
|
||||
}
|
||||
|
||||
ns.stack = append(ns.stack, v)
|
||||
}
|
||||
|
||||
// Pops a nonce from the stack.
|
||||
// Returns empty string if there are no nonces
|
||||
func (ns *nonceStack) pop() string {
|
||||
ns.lock.Lock()
|
||||
defer ns.lock.Unlock()
|
||||
|
||||
n := len(ns.stack)
|
||||
if n == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
v := ns.stack[n-1]
|
||||
ns.stack = ns.stack[:n-1]
|
||||
|
||||
return v
|
||||
}
|
||||
173
vendor/github.com/eggsampler/acme/v3/options.go
generated
vendored
Normal file
173
vendor/github.com/eggsampler/acme/v3/options.go
generated
vendored
Normal file
@@ -0,0 +1,173 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"crypto/hmac"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// OptionFunc function prototype for passing options to NewClient
|
||||
type OptionFunc func(client *Client) error
|
||||
|
||||
// WithHTTPTimeout sets a timeout on the http client used by the Client
|
||||
func WithHTTPTimeout(duration time.Duration) OptionFunc {
|
||||
return func(client *Client) error {
|
||||
client.httpClient.Timeout = duration
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithInsecureSkipVerify sets InsecureSkipVerify on the http client transport tls client config used by the Client
|
||||
func WithInsecureSkipVerify() OptionFunc {
|
||||
return func(client *Client) error {
|
||||
client.httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
InsecureSkipVerify: true,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithUserAgentSuffix appends a user agent suffix for http requests to acme resources
|
||||
func WithUserAgentSuffix(userAgentSuffix string) OptionFunc {
|
||||
return func(client *Client) error {
|
||||
client.userAgentSuffix = userAgentSuffix
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithAcceptLanguage sets an Accept-Language header on http requests
|
||||
func WithAcceptLanguage(acceptLanguage string) OptionFunc {
|
||||
return func(client *Client) error {
|
||||
client.acceptLanguage = acceptLanguage
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRetryCount sets the number of times the acme client retries when receiving an api error (eg, nonce failures, etc).
|
||||
// Default: 5
|
||||
func WithRetryCount(retryCount int) OptionFunc {
|
||||
return func(client *Client) error {
|
||||
if retryCount < 1 {
|
||||
return errors.New("retryCount must be > 0")
|
||||
}
|
||||
client.retryCount = retryCount
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithHTTPClient Allows setting a custom http client for acme connections
|
||||
func WithHTTPClient(httpClient *http.Client) OptionFunc {
|
||||
return func(client *Client) error {
|
||||
if httpClient == nil {
|
||||
return errors.New("client must not be nil")
|
||||
}
|
||||
client.httpClient = httpClient
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithRootCerts sets the httpclient transport to use a given certpool for root certs
|
||||
func WithRootCerts(pool *x509.CertPool) OptionFunc {
|
||||
return func(client *Client) error {
|
||||
client.httpClient.Transport = &http.Transport{
|
||||
TLSClientConfig: &tls.Config{
|
||||
RootCAs: pool,
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewAccountOptionFunc function prototype for passing options to NewClient
|
||||
type NewAccountOptionFunc func(crypto.Signer, *Account, *NewAccountRequest, Client) error
|
||||
|
||||
// NewAcctOptOnlyReturnExisting sets the new client request to only return existing accounts
|
||||
func NewAcctOptOnlyReturnExisting() NewAccountOptionFunc {
|
||||
return func(privateKey crypto.Signer, account *Account, request *NewAccountRequest, client Client) error {
|
||||
request.OnlyReturnExisting = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewAcctOptAgreeTOS sets the new account request as agreeing to the terms of service
|
||||
func NewAcctOptAgreeTOS() NewAccountOptionFunc {
|
||||
return func(privateKey crypto.Signer, account *Account, request *NewAccountRequest, client Client) error {
|
||||
request.TermsOfServiceAgreed = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewAcctOptWithContacts adds contacts to a new account request
|
||||
func NewAcctOptWithContacts(contacts ...string) NewAccountOptionFunc {
|
||||
return func(privateKey crypto.Signer, account *Account, request *NewAccountRequest, client Client) error {
|
||||
request.Contact = contacts
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewAcctOptExternalAccountBinding adds an external account binding to the new account request
|
||||
// Code adopted from jwsEncodeJSON
|
||||
func NewAcctOptExternalAccountBinding(binding ExternalAccountBinding) NewAccountOptionFunc {
|
||||
return func(privateKey crypto.Signer, account *Account, request *NewAccountRequest, client Client) error {
|
||||
if binding.KeyIdentifier == "" {
|
||||
return errors.New("acme: NewAcctOptExternalAccountBinding has no KeyIdentifier set")
|
||||
}
|
||||
if binding.MacKey == "" {
|
||||
return errors.New("acme: NewAcctOptExternalAccountBinding has no MacKey set")
|
||||
}
|
||||
if binding.Algorithm == "" {
|
||||
return errors.New("acme: NewAcctOptExternalAccountBinding has no Algorithm set")
|
||||
}
|
||||
if binding.HashFunc == 0 {
|
||||
return errors.New("acme: NewAcctOptExternalAccountBinding has no HashFunc set")
|
||||
}
|
||||
|
||||
jwk, err := jwkEncode(privateKey.Public())
|
||||
if err != nil {
|
||||
return fmt.Errorf("acme: external account binding error encoding public key: %v", err)
|
||||
}
|
||||
payload := base64.RawURLEncoding.EncodeToString([]byte(jwk))
|
||||
|
||||
phead := fmt.Sprintf(`{"alg":%q,"kid":%q,"url":%q}`,
|
||||
binding.Algorithm, binding.KeyIdentifier, client.Directory().NewAccount)
|
||||
phead = base64.RawURLEncoding.EncodeToString([]byte(phead))
|
||||
|
||||
decodedAccountMac, err := base64.RawURLEncoding.DecodeString(binding.MacKey)
|
||||
if err != nil {
|
||||
return fmt.Errorf("acme: external account binding error decoding mac key: %v", err)
|
||||
}
|
||||
macHash := hmac.New(binding.HashFunc.New, decodedAccountMac)
|
||||
|
||||
if _, err := macHash.Write([]byte(phead + "." + payload)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
enc := struct {
|
||||
Protected string `json:"protected"`
|
||||
Payload string `json:"payload"`
|
||||
Sig string `json:"signature"`
|
||||
}{
|
||||
Protected: phead,
|
||||
Payload: payload,
|
||||
Sig: base64.RawURLEncoding.EncodeToString(macHash.Sum(nil)),
|
||||
}
|
||||
|
||||
jwsEab, err := json.Marshal(&enc)
|
||||
if err != nil {
|
||||
return fmt.Errorf("acme: external account binding error marshalling struct: %v", err)
|
||||
}
|
||||
|
||||
request.ExternalAccountBinding = jwsEab
|
||||
account.ExternalAccountBinding = binding
|
||||
return nil
|
||||
}
|
||||
}
|
||||
231
vendor/github.com/eggsampler/acme/v3/order.go
generated
vendored
Normal file
231
vendor/github.com/eggsampler/acme/v3/order.go
generated
vendored
Normal file
@@ -0,0 +1,231 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
type OrderExtension struct {
|
||||
Profile string
|
||||
}
|
||||
|
||||
// NewOrder initiates a new order for a new certificate. This method does not use ACME Renewal Info.
|
||||
func (c Client) NewOrder(account Account, identifiers []Identifier) (Order, error) {
|
||||
return c.ReplacementOrder(account, nil, identifiers)
|
||||
}
|
||||
|
||||
// NewOrderDomains takes a list of domain dns identifiers for a new certificate. Essentially a helper function.
|
||||
func (c Client) NewOrderDomains(account Account, domains ...string) (Order, error) {
|
||||
var identifiers []Identifier
|
||||
for _, d := range domains {
|
||||
identifiers = append(identifiers, Identifier{Type: "dns", Value: d})
|
||||
}
|
||||
return c.ReplacementOrder(account, nil, identifiers)
|
||||
}
|
||||
|
||||
// NewOrderExtension takes a struct providing any extensions onto the order
|
||||
func (c Client) NewOrderExtension(account Account, identifiers []Identifier, ext OrderExtension) (Order, error) {
|
||||
return c.ReplacementOrderExtension(account, nil, identifiers, ext)
|
||||
}
|
||||
|
||||
// ReplacementOrder takes an existing *x509.Certificate and initiates a new
|
||||
// order for a new certificate, but with the order being marked as a
|
||||
// replacement. Replacement orders which are valid replacements are (currently)
|
||||
// exempt from Let's Encrypt NewOrder rate limits, but may not be exempt from
|
||||
// other ACME CAs ACME Renewal Info implementations. At least one identifier
|
||||
// must match the list of identifiers from the parent order to be considered as
|
||||
// a valid replacement order.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||
func (c Client) ReplacementOrder(account Account, oldCert *x509.Certificate, identifiers []Identifier) (Order, error) {
|
||||
return c.ReplacementOrderExtension(account, oldCert, identifiers, OrderExtension{})
|
||||
}
|
||||
|
||||
// ReplacementOrderExtension takes a struct providing any extensions onto the order
|
||||
func (c Client) ReplacementOrderExtension(account Account, oldCert *x509.Certificate, identifiers []Identifier, ext OrderExtension) (Order, error) {
|
||||
// If an old cert being replaced is present and the acme directory doesn't list a RenewalInfo endpoint,
|
||||
// throw an error. This endpoint being present indicates support for ARI.
|
||||
if oldCert != nil && c.dir.RenewalInfo == "" {
|
||||
return Order{}, ErrRenewalInfoNotSupported
|
||||
}
|
||||
|
||||
// optional fields are listed as 'omitempty' so the json encoder doesn't
|
||||
// include those keys if their values are not provided.
|
||||
newOrderReq := struct {
|
||||
Identifiers []Identifier `json:"identifiers"`
|
||||
Replaces string `json:"replaces,omitempty"`
|
||||
Profile string `json:"Profile,omitempty"`
|
||||
}{
|
||||
Identifiers: identifiers,
|
||||
}
|
||||
|
||||
newOrderResp := Order{}
|
||||
|
||||
if ext.Profile != "" {
|
||||
_, ok := c.Directory().Meta.Profiles[ext.Profile]
|
||||
if !ok {
|
||||
return Order{}, fmt.Errorf("requested Profile not advertised by directory: %v", ext.Profile)
|
||||
}
|
||||
newOrderReq.Profile = ext.Profile
|
||||
}
|
||||
|
||||
// If present, add the ari cert ID from the original/old certificate
|
||||
if oldCert != nil {
|
||||
replacesCertID, err := GenerateARICertID(oldCert)
|
||||
if err != nil {
|
||||
return Order{}, fmt.Errorf("acme: error generating replacement certificate id: %v", err)
|
||||
}
|
||||
|
||||
newOrderReq.Replaces = replacesCertID
|
||||
newOrderResp.Replaces = replacesCertID // server does not appear to set this currently?
|
||||
}
|
||||
|
||||
// Submit the order
|
||||
resp, err := c.post(c.dir.NewOrder, account.URL, account.PrivateKey, newOrderReq, &newOrderResp, http.StatusCreated)
|
||||
if err != nil {
|
||||
return newOrderResp, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
newOrderResp.URL = resp.Header.Get("Location")
|
||||
return newOrderResp, nil
|
||||
}
|
||||
|
||||
// FetchOrder fetches an existing order given an order url.
|
||||
func (c Client) FetchOrder(account Account, orderURL string) (Order, error) {
|
||||
orderResp := Order{
|
||||
URL: orderURL, // boulder response doesn't seem to contain location header for this request
|
||||
}
|
||||
_, err := c.post(orderURL, account.URL, account.PrivateKey, "", &orderResp, http.StatusOK)
|
||||
|
||||
return orderResp, err
|
||||
}
|
||||
|
||||
// Helper function to determine whether an order is "finished" by its status.
|
||||
func checkFinalizedOrderStatus(order Order) (bool, error) {
|
||||
switch order.Status {
|
||||
case "invalid":
|
||||
// "invalid": The certificate will not be issued. Consider this
|
||||
// order process abandoned.
|
||||
if order.Error.Type != "" {
|
||||
return true, order.Error
|
||||
}
|
||||
return true, errors.New("acme: finalized order is invalid, no error provided")
|
||||
|
||||
case "pending":
|
||||
// "pending": The server does not believe that the client has
|
||||
// fulfilled the requirements. Check the "authorizations" array for
|
||||
// entries that are still pending.
|
||||
return true, errors.New("acme: authorizations not fulfilled")
|
||||
|
||||
case "ready":
|
||||
// "ready": The server agrees that the requirements have been
|
||||
// fulfilled, and is awaiting finalization. Submit a finalization
|
||||
// request.
|
||||
return true, errors.New("acme: unexpected 'ready' state")
|
||||
|
||||
case "processing":
|
||||
// "processing": The certificate is being issued. Send a GET request
|
||||
// after the time given in the "Retry-After" header field of the
|
||||
// response, if any.
|
||||
return false, nil
|
||||
|
||||
case "valid":
|
||||
// "valid": The server has issued the certificate and provisioned its
|
||||
// URL to the "certificate" field of the order. Download the
|
||||
// certificate.
|
||||
return true, nil
|
||||
|
||||
default:
|
||||
return true, fmt.Errorf("acme: unknown order status: %s", order.Status)
|
||||
}
|
||||
}
|
||||
|
||||
// FinalizeOrder indicates to the acme server that the client considers an order complete and "finalizes" it.
|
||||
// If the server believes the authorizations have been filled successfully, a certificate should then be available.
|
||||
// This function assumes that the order status is "ready".
|
||||
func (c Client) FinalizeOrder(account Account, order Order, csr *x509.CertificateRequest) (Order, error) {
|
||||
finaliseReq := struct {
|
||||
Csr string `json:"csr"`
|
||||
}{
|
||||
Csr: base64.RawURLEncoding.EncodeToString(csr.Raw),
|
||||
}
|
||||
|
||||
resp, err := c.post(order.Finalize, account.URL, account.PrivateKey, finaliseReq, &order, http.StatusOK)
|
||||
if err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
order.URL = resp.Header.Get("Location")
|
||||
|
||||
updateOrder := func(resp *http.Response) (bool, error) {
|
||||
if finished, err := checkFinalizedOrderStatus(order); finished {
|
||||
return true, err
|
||||
}
|
||||
|
||||
retryAfter, err := parseRetryAfter(resp.Header.Get("Retry-After"))
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("acme: error parsing retry-after header: %v", err)
|
||||
}
|
||||
order.RetryAfter = retryAfter
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if finished, err := updateOrder(resp); finished || err != nil {
|
||||
return order, err
|
||||
}
|
||||
|
||||
fetchOrder := func() (bool, error) {
|
||||
resp, err := c.post(order.URL, account.URL, account.PrivateKey, "", &order, http.StatusOK)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return updateOrder(resp)
|
||||
}
|
||||
|
||||
if !c.IgnoreRetryAfter && !order.RetryAfter.IsZero() {
|
||||
_, pollTimeout := c.getPollingDurations()
|
||||
end := time.Now().Add(pollTimeout)
|
||||
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
return order, errors.New("acme: finalized order timeout")
|
||||
}
|
||||
|
||||
diff := time.Until(order.RetryAfter)
|
||||
_, pollTimeout := c.getPollingDurations()
|
||||
if diff > pollTimeout {
|
||||
return order, fmt.Errorf("acme: Retry-After (%v) longer than poll timeout (%v)", diff, c.PollTimeout)
|
||||
}
|
||||
if diff > 0 {
|
||||
time.Sleep(diff)
|
||||
}
|
||||
|
||||
if finished, err := fetchOrder(); finished || err != nil {
|
||||
return order, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !c.IgnoreRetryAfter {
|
||||
pollInterval, pollTimeout := c.getPollingDurations()
|
||||
end := time.Now().Add(pollTimeout)
|
||||
for {
|
||||
if time.Now().After(end) {
|
||||
return order, errors.New("acme: finalized order timeout")
|
||||
}
|
||||
time.Sleep(pollInterval)
|
||||
|
||||
if finished, err := fetchOrder(); finished || err != nil {
|
||||
return order, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return order, err
|
||||
}
|
||||
65
vendor/github.com/eggsampler/acme/v3/problem.go
generated
vendored
Normal file
65
vendor/github.com/eggsampler/acme/v3/problem.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Problem document as defined in,
|
||||
// https://tools.ietf.org/html/rfc7807
|
||||
|
||||
// Problem represents an error returned by an acme server.
|
||||
type Problem struct {
|
||||
Type string `json:"type"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
Status int `json:"status,omitempty"`
|
||||
Instance string `json:"instance,omitempty"`
|
||||
SubProblems []SubProblem `json:"subproblems,omitempty"`
|
||||
}
|
||||
|
||||
type SubProblem struct {
|
||||
Type string `json:"type"`
|
||||
Detail string `json:"detail"`
|
||||
Identifier Identifier `json:"identifier"`
|
||||
}
|
||||
|
||||
// Returns a human readable error string.
|
||||
func (err Problem) Error() string {
|
||||
s := fmt.Sprintf("acme: error code %d %q: %s", err.Status, err.Type, err.Detail)
|
||||
if len(err.SubProblems) > 0 {
|
||||
for _, v := range err.SubProblems {
|
||||
s += fmt.Sprintf(", problem %q: %s", v.Type, v.Detail)
|
||||
}
|
||||
}
|
||||
if err.Instance != "" {
|
||||
s += ", url: " + err.Instance
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Helper function to determine if a response contains an expected status code, or otherwise an error object.
|
||||
func checkError(resp *http.Response, expectedStatuses ...int) error {
|
||||
for _, statusCode := range expectedStatuses {
|
||||
if resp.StatusCode == statusCode {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if resp.StatusCode < 400 || resp.StatusCode >= 600 {
|
||||
return fmt.Errorf("acme: expected status codes: %d, got: %d %s", expectedStatuses, resp.StatusCode, resp.Status)
|
||||
}
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return fmt.Errorf("acme: error reading error body: %v", err)
|
||||
}
|
||||
|
||||
acmeError := Problem{}
|
||||
if err := json.Unmarshal(body, &acmeError); err != nil {
|
||||
return fmt.Errorf("acme: parsing error body: %v - %s", err, string(body))
|
||||
}
|
||||
|
||||
return acmeError
|
||||
}
|
||||
236
vendor/github.com/eggsampler/acme/v3/types.go
generated
vendored
Normal file
236
vendor/github.com/eggsampler/acme/v3/types.go
generated
vendored
Normal file
@@ -0,0 +1,236 @@
|
||||
package acme
|
||||
|
||||
import (
|
||||
"crypto"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrUnsupportedKey is returned when an unsupported key type is encountered.
|
||||
ErrUnsupportedKey = errors.New("acme: unknown key type; only RSA and ECDSA are supported")
|
||||
|
||||
// ErrRenewalInfoNotSupported is returned by Client.GetRenewalInfo if the
|
||||
// renewal info entry isn't present on the acme directory (ie, it's not
|
||||
// supported by the acme server)
|
||||
ErrRenewalInfoNotSupported = errors.New("renewal information endpoint not supported")
|
||||
)
|
||||
|
||||
// Different possible challenge types provided by an ACME server.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-9.7.8
|
||||
const (
|
||||
ChallengeTypeDNS01 = "dns-01"
|
||||
ChallengeTypeDNSAccount01 = "dns-account-01"
|
||||
ChallengeTypeHTTP01 = "http-01"
|
||||
ChallengeTypeTLSALPN01 = "tls-alpn-01"
|
||||
|
||||
// ChallengeTypeTLSSNI01 is deprecated and should not be used.
|
||||
// See: https://community.letsencrypt.org/t/important-what-you-need-to-know-about-tls-sni-validation-issues/50811
|
||||
ChallengeTypeTLSSNI01 = "tls-sni-01"
|
||||
)
|
||||
|
||||
// Constants used for certificate revocation, used for RevokeCertificate
|
||||
// See https://tools.ietf.org/html/rfc5280#section-5.3.1
|
||||
const (
|
||||
ReasonUnspecified = iota // 0
|
||||
ReasonKeyCompromise // 1
|
||||
ReasonCaCompromise // 2
|
||||
ReasonAffiliationChanged // 3
|
||||
ReasonSuperseded // 4
|
||||
ReasonCessationOfOperation // 5
|
||||
ReasonCertificateHold // 6
|
||||
_ // 7 - Unused
|
||||
ReasonRemoveFromCRL // 8
|
||||
ReasonPrivilegeWithdrawn // 9
|
||||
ReasonAaCompromise // 10
|
||||
)
|
||||
|
||||
// Directory object as returned from the client's directory url upon creation of client.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.1
|
||||
type Directory struct {
|
||||
NewNonce string `json:"newNonce"` // url to new nonce endpoint
|
||||
NewAccount string `json:"newAccount"` // url to new account endpoint
|
||||
NewOrder string `json:"newOrder"` // url to new order endpoint
|
||||
NewAuthz string `json:"newAuthz"` // url to new authz endpoint
|
||||
RevokeCert string `json:"revokeCert"` // url to revoke cert endpoint
|
||||
KeyChange string `json:"keyChange"` // url to key change endpoint
|
||||
|
||||
// https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03
|
||||
RenewalInfo string `json:"renewalInfo"` // url to renewal info endpoint
|
||||
|
||||
// meta object containing directory metadata
|
||||
Meta struct {
|
||||
TermsOfService string `json:"termsOfService"`
|
||||
Website string `json:"website"`
|
||||
CaaIdentities []string `json:"caaIdentities"`
|
||||
ExternalAccountRequired bool `json:"externalAccountRequired"`
|
||||
Profiles map[string]string `json:"profiles"`
|
||||
} `json:"meta"`
|
||||
|
||||
// Directory url provided when creating a new acme client.
|
||||
URL string `json:"-"`
|
||||
}
|
||||
|
||||
// Client structure to interact with an ACME server.
|
||||
// This is typically how most, if not all, of the communication between the client and server occurs.
|
||||
type Client struct {
|
||||
httpClient *http.Client
|
||||
nonces *nonceStack
|
||||
dir Directory
|
||||
userAgentSuffix string
|
||||
acceptLanguage string
|
||||
retryCount int
|
||||
|
||||
// The amount of total time the Client will wait at most for a challenge to be updated or a certificate to be issued.
|
||||
// Default 30 seconds if duration is not set or if set to 0.
|
||||
PollTimeout time.Duration
|
||||
|
||||
// The time between checking if a challenge has been updated or a certificate has been issued.
|
||||
// Default 0.5 seconds if duration is not set or if set to 0.
|
||||
PollInterval time.Duration
|
||||
|
||||
// IgnorePolling does not use any simple polling in order finalisation
|
||||
IgnorePolling bool
|
||||
|
||||
// IgnoreRetryAfter does not use the retry-after header in order finalisation
|
||||
IgnoreRetryAfter bool
|
||||
}
|
||||
|
||||
// Account structure representing fields in an account object.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.2
|
||||
// See also https://tools.ietf.org/html/rfc8555#section-9.7.1
|
||||
type Account struct {
|
||||
Status string `json:"status"`
|
||||
Contact []string `json:"contact"`
|
||||
Orders string `json:"orders"`
|
||||
|
||||
// Provided by the Location http header when creating a new account or fetching an existing account.
|
||||
URL string `json:"-"`
|
||||
|
||||
// The private key used to create or fetch the account.
|
||||
// Not fetched from server.
|
||||
PrivateKey crypto.Signer `json:"-"`
|
||||
|
||||
// Thumbprint is the SHA-256 digest JWK_Thumbprint of the account key.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-8.1
|
||||
Thumbprint string `json:"-"`
|
||||
|
||||
// ExternalAccountBinding is populated when using the NewAcctOptExternalAccountBinding option for NewAccountOption
|
||||
// and is otherwise empty. Not populated when account is fetched or created otherwise.
|
||||
ExternalAccountBinding ExternalAccountBinding `json:"-"`
|
||||
}
|
||||
|
||||
// ExternalAccountBinding holds the key identifier and mac key provided for use in servers that support/require
|
||||
// external account binding.
|
||||
// The MacKey is a base64url-encoded string.
|
||||
// Algorithm is a "MAC-based algorithm" as per RFC8555. Typically this is either,
|
||||
// - "HS256" for HashFunc: crypto.SHA256
|
||||
// - "HS384" for HashFunc: crypto.SHA384
|
||||
// - "HS512" for HashFunc: crypto.SHA512
|
||||
//
|
||||
// However this is dependent on the acme server in question and is provided here to give more options for future compatibility.
|
||||
type ExternalAccountBinding struct {
|
||||
KeyIdentifier string `json:"-"`
|
||||
MacKey string `json:"-"`
|
||||
Algorithm string `json:"-"`
|
||||
HashFunc crypto.Hash `json:"-"`
|
||||
}
|
||||
|
||||
// Identifier object used in order and authorization objects
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||
type Identifier struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// Order object returned when fetching or creating a new order.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.3
|
||||
type Order struct {
|
||||
Status string `json:"status"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Identifiers []Identifier `json:"identifiers"`
|
||||
Profile string `json:"Profile,omitempty"`
|
||||
NotBefore time.Time `json:"notBefore"`
|
||||
NotAfter time.Time `json:"notAfter"`
|
||||
Error Problem `json:"error"`
|
||||
Authorizations []string `json:"authorizations"`
|
||||
Finalize string `json:"finalize"`
|
||||
Certificate string `json:"certificate"`
|
||||
|
||||
// URL for the order object.
|
||||
// Provided by the rel="Location" Link http header
|
||||
URL string `json:"-"`
|
||||
|
||||
// RetryAfter is the http Retry-After header from the order response
|
||||
RetryAfter time.Time `json:"-"`
|
||||
|
||||
// Replaces (optional, string): A string uniquely identifying a
|
||||
// previously-issued certificate which this order is intended to replace.
|
||||
// See https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-5
|
||||
Replaces string `json:"replaces,omitempty"`
|
||||
}
|
||||
|
||||
// Authorization object returned when fetching an authorization in an order.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.4
|
||||
type Authorization struct {
|
||||
Identifier Identifier `json:"identifier"`
|
||||
Status string `json:"status"`
|
||||
Expires time.Time `json:"expires"`
|
||||
Challenges []Challenge `json:"challenges"`
|
||||
Wildcard bool `json:"wildcard"`
|
||||
|
||||
// For convenience access to the provided challenges
|
||||
ChallengeMap map[string]Challenge `json:"-"`
|
||||
ChallengeTypes []string `json:"-"`
|
||||
|
||||
URL string `json:"-"`
|
||||
}
|
||||
|
||||
// Challenge object fetched in an authorization or directly from the challenge url.
|
||||
// See https://tools.ietf.org/html/rfc8555#section-7.1.5
|
||||
type Challenge struct {
|
||||
Type string `json:"type"`
|
||||
URL string `json:"url"`
|
||||
Status string `json:"status"`
|
||||
Validated string `json:"validated"`
|
||||
Error Problem `json:"error"`
|
||||
|
||||
// Based on the challenge used
|
||||
Token string `json:"token"`
|
||||
KeyAuthorization string `json:"keyAuthorization"`
|
||||
|
||||
// Authorization url provided by the rel="up" Link http header
|
||||
AuthorizationURL string `json:"-"`
|
||||
}
|
||||
|
||||
// OrderList of challenge objects.
|
||||
type OrderList struct {
|
||||
Orders []string `json:"orders"`
|
||||
|
||||
// Order list pagination, url to next orders.
|
||||
// Provided by the rel="next" Link http header
|
||||
Next string `json:"-"`
|
||||
}
|
||||
|
||||
// NewAccountRequest object used for submitting a request for a new account.
|
||||
// Primarily used with NewAccountOptionFunc
|
||||
type NewAccountRequest struct {
|
||||
OnlyReturnExisting bool `json:"onlyReturnExisting"`
|
||||
TermsOfServiceAgreed bool `json:"termsOfServiceAgreed"`
|
||||
Contact []string `json:"contact,omitempty"`
|
||||
ExternalAccountBinding json.RawMessage `json:"externalAccountBinding"`
|
||||
}
|
||||
|
||||
// RenewalInfo stores the server-provided suggestions on when to renew
|
||||
// certificates.
|
||||
type RenewalInfo struct {
|
||||
SuggestedWindow struct {
|
||||
Start time.Time `json:"start"`
|
||||
End time.Time `json:"end"`
|
||||
} `json:"suggestedWindow"`
|
||||
ExplanationURL string `json:"explanationURL,omitempty"`
|
||||
|
||||
RetryAfter time.Time `json:"-"`
|
||||
}
|
||||
7
vendor/modules.txt
vendored
7
vendor/modules.txt
vendored
@@ -518,6 +518,9 @@ github.com/eapache/queue
|
||||
# github.com/edsrzf/mmap-go v1.0.0
|
||||
## explicit
|
||||
github.com/edsrzf/mmap-go
|
||||
# github.com/eggsampler/acme/v3 v3.6.2
|
||||
## explicit; go 1.11
|
||||
github.com/eggsampler/acme/v3
|
||||
# github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a
|
||||
## explicit
|
||||
github.com/elgatito/upnp
|
||||
@@ -1849,7 +1852,7 @@ sigs.k8s.io/structured-merge-diff/v4/value
|
||||
# sigs.k8s.io/yaml v1.2.0
|
||||
## explicit; go 1.12
|
||||
sigs.k8s.io/yaml
|
||||
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250704072708-d4757966c574
|
||||
# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20250714064001-1e1f50030bed
|
||||
## explicit; go 1.21
|
||||
yunion.io/x/cloudmux/pkg/apis
|
||||
yunion.io/x/cloudmux/pkg/apis/billing
|
||||
@@ -1946,7 +1949,7 @@ yunion.io/x/log/hooks
|
||||
yunion.io/x/ovsdb/cli_util
|
||||
yunion.io/x/ovsdb/schema/ovn_nb
|
||||
yunion.io/x/ovsdb/types
|
||||
# yunion.io/x/pkg v1.10.4-0.20250618071935-840c253075b0
|
||||
# yunion.io/x/pkg v1.10.4-0.20250712095850-7797c1f3ffa6
|
||||
## explicit; go 1.18
|
||||
yunion.io/x/pkg/appctx
|
||||
yunion.io/x/pkg/errors
|
||||
|
||||
5
vendor/yunion.io/x/cloudmux/pkg/cloudprovider/cloudprovider.go
generated
vendored
5
vendor/yunion.io/x/cloudmux/pkg/cloudprovider/cloudprovider.go
generated
vendored
@@ -310,6 +310,7 @@ type ICloudProvider interface {
|
||||
GetMetrics(opts *MetricListOptions) ([]MetricValues, error)
|
||||
|
||||
GetISSLCertificates() ([]ICloudSSLCertificate, error)
|
||||
CreateISSLCertificate(opts *SSLCertificateCreateOptions) (ICloudSSLCertificate, error)
|
||||
|
||||
GetIAiGateways() ([]IAiGateway, error)
|
||||
GetIAiGatewayById(id string) (IAiGateway, error)
|
||||
@@ -643,6 +644,10 @@ func (base *SBaseProvider) GetISSLCertificates() ([]ICloudSSLCertificate, error)
|
||||
return nil, errors.Wrapf(ErrNotImplemented, "GetISSLCertificates")
|
||||
}
|
||||
|
||||
func (base *SBaseProvider) CreateISSLCertificate(opts *SSLCertificateCreateOptions) (ICloudSSLCertificate, error) {
|
||||
return nil, errors.Wrapf(ErrNotImplemented, "CreateISSLCertificate")
|
||||
}
|
||||
|
||||
func (base *SBaseProvider) GetIAiGateways() ([]IAiGateway, error) {
|
||||
return nil, errors.Wrapf(ErrNotImplemented, "GetIAiGateways")
|
||||
}
|
||||
|
||||
1
vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go
generated
vendored
1
vendor/yunion.io/x/cloudmux/pkg/cloudprovider/resources.go
generated
vendored
@@ -1814,7 +1814,6 @@ type ICloudSSLCertificate interface {
|
||||
GetCommon() string
|
||||
GetCountry() string
|
||||
GetIssuer() string
|
||||
GetExpired() bool
|
||||
GetEndDate() time.Time
|
||||
GetFingerprint() string
|
||||
GetCity() string
|
||||
|
||||
25
vendor/yunion.io/x/cloudmux/pkg/cloudprovider/ssl.go
generated
vendored
Normal file
25
vendor/yunion.io/x/cloudmux/pkg/cloudprovider/ssl.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2019 Yunion
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package cloudprovider
|
||||
|
||||
const (
|
||||
SSL_ISSUER_LETSENCRYPT = "Let's Encrypt"
|
||||
SSL_ISSUER_ZEROSSL = "ZeroSSL"
|
||||
)
|
||||
|
||||
type SSLCertificateCreateOptions struct {
|
||||
Certificate string
|
||||
PrivateKey string
|
||||
}
|
||||
4
vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/sslcertificate.go
generated
vendored
4
vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/sslcertificate.go
generated
vendored
@@ -59,10 +59,6 @@ func (s *SSSLCertificate) GetIssuer() string {
|
||||
return s.Issuer
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetExpired() bool {
|
||||
return s.Expired
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetEndDate() time.Time {
|
||||
return s.EndDate
|
||||
}
|
||||
|
||||
2
vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/storage.go
generated
vendored
2
vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/storage.go
generated
vendored
@@ -709,7 +709,7 @@ func (self *SDatastore) CheckFile(ctx context.Context, remotePath string) (*SDat
|
||||
|
||||
req, err := http.NewRequest("HEAD", url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "HEAD %s", url)
|
||||
}
|
||||
|
||||
var size uint64
|
||||
|
||||
3
vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/virtualmachine.go
generated
vendored
3
vendor/yunion.io/x/cloudmux/pkg/multicloud/esxi/virtualmachine.go
generated
vendored
@@ -684,6 +684,7 @@ func (svm *SVirtualMachine) doUnregister(ctx context.Context) error {
|
||||
func (svm *SVirtualMachine) DeleteVM(ctx context.Context) error {
|
||||
err := svm.CheckFileInfo(ctx)
|
||||
if err != nil {
|
||||
log.Errorf("CheckFileInfo %s error: %v", svm.GetName(), err)
|
||||
return svm.doUnregister(ctx)
|
||||
}
|
||||
return svm.doDestroy(ctx)
|
||||
@@ -1355,7 +1356,7 @@ func (svm *SVirtualMachine) CheckFileInfo(ctx context.Context) error {
|
||||
if ds.HasFile(file.Name) {
|
||||
_, err := ds.CheckFile(ctx, file.Name)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "ds.CheckFile")
|
||||
return errors.Wrapf(err, "CheckFile %s", file.Name)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
10
vendor/yunion.io/x/cloudmux/pkg/multicloud/google/eip.go
generated
vendored
10
vendor/yunion.io/x/cloudmux/pkg/multicloud/google/eip.go
generated
vendored
@@ -125,9 +125,17 @@ func (addr *SAddress) GetINetworkId() string {
|
||||
}
|
||||
|
||||
func (addr *SAddress) GetAssociationType() string {
|
||||
if len(addr.GetAssociationExternalId()) > 0 {
|
||||
if len(addr.instanceId) > 0 {
|
||||
return api.EIP_ASSOCIATE_TYPE_SERVER
|
||||
}
|
||||
for _, user := range addr.Users {
|
||||
if strings.HasPrefix(user, "/instances/") {
|
||||
return api.EIP_ASSOCIATE_TYPE_SERVER
|
||||
}
|
||||
if strings.Contains(user, "/forwardingRules/") {
|
||||
return api.EIP_ASSOCIATE_TYPE_LOADBALANCER
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
|
||||
7
vendor/yunion.io/x/cloudmux/pkg/multicloud/google/loadbalancer.go
generated
vendored
7
vendor/yunion.io/x/cloudmux/pkg/multicloud/google/loadbalancer.go
generated
vendored
@@ -23,6 +23,7 @@ import (
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
"yunion.io/x/pkg/errors"
|
||||
"yunion.io/x/pkg/util/netutils"
|
||||
"yunion.io/x/pkg/utils"
|
||||
|
||||
api "yunion.io/x/cloudmux/pkg/apis/compute"
|
||||
@@ -248,9 +249,11 @@ func (self *SLoadbalancer) GetIEIPs() ([]cloudprovider.ICloudEIP, error) {
|
||||
log.Errorf("GetAddress.GetForwardingRules %s", err)
|
||||
}
|
||||
|
||||
ret := []cloudprovider.ICloudEIP{}
|
||||
ret, addrs := []cloudprovider.ICloudEIP{}, []string{}
|
||||
for i := range frs {
|
||||
if strings.ToLower(frs[i].LoadBalancingScheme) == "external" {
|
||||
ipAddr, _ := netutils.NewIPV4Addr(frs[i].IPAddress)
|
||||
if netutils.IsExitAddress(ipAddr) && !utils.IsInStringArray(frs[i].IPAddress, addrs) {
|
||||
addrs = append(addrs, frs[i].IPAddress)
|
||||
eips, err := self.region.GetEips(frs[i].IPAddress, 0, "")
|
||||
if err != nil {
|
||||
log.Errorf("GetEips %s", err)
|
||||
|
||||
8
vendor/yunion.io/x/cloudmux/pkg/multicloud/google/loadbalancer_components.go
generated
vendored
8
vendor/yunion.io/x/cloudmux/pkg/multicloud/google/loadbalancer_components.go
generated
vendored
@@ -559,15 +559,15 @@ func (self *SLoadbalancer) GetForwardingRules() ([]SForwardingRule, error) {
|
||||
|
||||
targets := make([]string, 0)
|
||||
for i := range hps {
|
||||
targets = append(targets, fmt.Sprintf(`(target="%s")`, hps[i].GetId()))
|
||||
targets = append(targets, fmt.Sprintf(`(target = "%s")`, hps[i].SelfLink))
|
||||
}
|
||||
|
||||
for i := range hsps {
|
||||
targets = append(targets, fmt.Sprintf(`(target="%s")`, hsps[i].GetId()))
|
||||
targets = append(targets, fmt.Sprintf(`(target = "%s")`, hsps[i].SelfLink))
|
||||
}
|
||||
|
||||
if strings.Contains(self.GetId(), "/backendServices/") {
|
||||
targets = append(targets, fmt.Sprintf(`(backendService="%s")`, self.GetId()))
|
||||
if strings.Contains(self.SelfLink, "/backendServices/") {
|
||||
targets = append(targets, fmt.Sprintf(`(backendService = "%s")`, self.SelfLink))
|
||||
}
|
||||
|
||||
if len(targets) == 0 {
|
||||
|
||||
11
vendor/yunion.io/x/cloudmux/pkg/multicloud/huawei/sslcertificate.go
generated
vendored
11
vendor/yunion.io/x/cloudmux/pkg/multicloud/huawei/sslcertificate.go
generated
vendored
@@ -9,6 +9,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/cloudmux/pkg/apis"
|
||||
"yunion.io/x/cloudmux/pkg/multicloud"
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
@@ -66,10 +67,6 @@ func (s *SSSLCertificate) GetIssuer() string {
|
||||
return s.Brand
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetExpired() bool {
|
||||
return time.Now().After(s.GetEndDate())
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetEndDate() time.Time {
|
||||
t, _ := time.Parse("2006-01-02 15:04:05", s.ExpireTime)
|
||||
return t
|
||||
@@ -111,11 +108,7 @@ func (s *SSSLCertificate) GetGlobalId() string {
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetStatus() string {
|
||||
if s.GetExpired() {
|
||||
return "expired"
|
||||
} else {
|
||||
return "normal"
|
||||
}
|
||||
return apis.STATUS_AVAILABLE
|
||||
}
|
||||
|
||||
func (s *SSSLCertificate) GetIsUpload() bool {
|
||||
|
||||
8
vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/certificate.go
generated
vendored
8
vendor/yunion.io/x/cloudmux/pkg/multicloud/qcloud/certificate.go
generated
vendored
@@ -220,14 +220,6 @@ func (self *SCertificate) GetIssuer() string {
|
||||
return self.ProductZhName
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetExpired() bool {
|
||||
if self.Status == 3 {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (self *SCertificate) GetEndDate() time.Time {
|
||||
return self.CERTEndTime
|
||||
}
|
||||
|
||||
52
vendor/yunion.io/x/pkg/utils/misc.go
generated
vendored
52
vendor/yunion.io/x/pkg/utils/misc.go
generated
vendored
@@ -24,6 +24,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
type selectFunc func(obj interface{}) (string, error)
|
||||
@@ -535,6 +537,31 @@ func GetSizeKB(sizeStr, defaultSize string) (int64, error) {
|
||||
return bytes / 1024, nil
|
||||
}
|
||||
|
||||
func transMysqlQuery(dburl string) (string, error) {
|
||||
queryPos := strings.IndexByte(dburl, '?')
|
||||
if queryPos == 0 {
|
||||
return "", fmt.Errorf("Missing database name")
|
||||
}
|
||||
var query url.Values
|
||||
if queryPos > 0 {
|
||||
queryStr := dburl[queryPos+1:]
|
||||
if len(queryStr) > 0 {
|
||||
var err error
|
||||
query, err = url.ParseQuery(queryStr)
|
||||
if err != nil {
|
||||
return "", errors.Wrap(err, "ParseQuery")
|
||||
}
|
||||
}
|
||||
dburl = dburl[:queryPos]
|
||||
} else {
|
||||
query = url.Values{}
|
||||
}
|
||||
query.Set("parseTime", "true")
|
||||
query.Set("charset", "utf8mb4")
|
||||
query.Set("interpolateParams", "true")
|
||||
return dburl + "?" + query.Encode(), nil
|
||||
}
|
||||
|
||||
func TransSQLAchemyURL(pySQLSrc string) (dialect, ret string, err error) {
|
||||
if len(pySQLSrc) == 0 {
|
||||
err = fmt.Errorf("Empty input")
|
||||
@@ -543,6 +570,10 @@ func TransSQLAchemyURL(pySQLSrc string) (dialect, ret string, err error) {
|
||||
|
||||
dialect = "mysql"
|
||||
if !strings.Contains(pySQLSrc, `//`) {
|
||||
pySQLSrc, err = transMysqlQuery(pySQLSrc)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
return dialect, pySQLSrc, nil
|
||||
}
|
||||
|
||||
@@ -560,26 +591,11 @@ func TransSQLAchemyURL(pySQLSrc string) (dialect, ret string, err error) {
|
||||
return
|
||||
}
|
||||
user, passwd, host, port, dburl := strs[1], strs[2], strs[3], strs[4], strs[5]
|
||||
queryPos := strings.IndexByte(dburl, '?')
|
||||
if queryPos == 0 {
|
||||
err = fmt.Errorf("Missing database name")
|
||||
dburl, err = transMysqlQuery(dburl)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
var query url.Values
|
||||
if queryPos > 0 {
|
||||
queryStr := dburl[queryPos+1:]
|
||||
if len(queryStr) > 0 {
|
||||
query, err = url.ParseQuery(queryStr)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
dburl = dburl[:queryPos]
|
||||
} else {
|
||||
query = url.Values{}
|
||||
}
|
||||
query.Set("parseTime", "True")
|
||||
ret = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?%s", user, passwd, host, port, dburl, query.Encode())
|
||||
ret = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", user, passwd, host, port, dburl)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user