Refactor ACME support to certificate provider
This commit is contained in:
106
option/acme.go
Normal file
106
option/acme.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
C "github.com/sagernet/sing-box/constant"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
)
|
||||
|
||||
type ACMECertificateProviderOptions struct {
|
||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||
DataDirectory string `json:"data_directory,omitempty"`
|
||||
DefaultServerName string `json:"default_server_name,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
AccountKey string `json:"account_key,omitempty"`
|
||||
DisableHTTPChallenge bool `json:"disable_http_challenge,omitempty"`
|
||||
DisableTLSALPNChallenge bool `json:"disable_tls_alpn_challenge,omitempty"`
|
||||
AlternativeHTTPPort uint16 `json:"alternative_http_port,omitempty"`
|
||||
AlternativeTLSPort uint16 `json:"alternative_tls_port,omitempty"`
|
||||
ExternalAccount *ACMEExternalAccountOptions `json:"external_account,omitempty"`
|
||||
DNS01Challenge *ACMEProviderDNS01ChallengeOptions `json:"dns01_challenge,omitempty"`
|
||||
KeyType ACMEKeyType `json:"key_type,omitempty"`
|
||||
Detour string `json:"detour,omitempty"`
|
||||
}
|
||||
|
||||
type _ACMEProviderDNS01ChallengeOptions struct {
|
||||
TTL badoption.Duration `json:"ttl,omitempty"`
|
||||
PropagationDelay badoption.Duration `json:"propagation_delay,omitempty"`
|
||||
PropagationTimeout badoption.Duration `json:"propagation_timeout,omitempty"`
|
||||
Resolvers badoption.Listable[string] `json:"resolvers,omitempty"`
|
||||
OverrideDomain string `json:"override_domain,omitempty"`
|
||||
Provider string `json:"provider,omitempty"`
|
||||
AliDNSOptions ACMEDNS01AliDNSOptions `json:"-"`
|
||||
CloudflareOptions ACMEDNS01CloudflareOptions `json:"-"`
|
||||
ACMEDNSOptions ACMEDNS01ACMEDNSOptions `json:"-"`
|
||||
}
|
||||
|
||||
type ACMEProviderDNS01ChallengeOptions _ACMEProviderDNS01ChallengeOptions
|
||||
|
||||
func (o ACMEProviderDNS01ChallengeOptions) MarshalJSON() ([]byte, error) {
|
||||
var v any
|
||||
switch o.Provider {
|
||||
case C.DNSProviderAliDNS:
|
||||
v = o.AliDNSOptions
|
||||
case C.DNSProviderCloudflare:
|
||||
v = o.CloudflareOptions
|
||||
case C.DNSProviderACMEDNS:
|
||||
v = o.ACMEDNSOptions
|
||||
case "":
|
||||
return nil, E.New("missing provider type")
|
||||
default:
|
||||
return nil, E.New("unknown provider type: ", o.Provider)
|
||||
}
|
||||
return badjson.MarshallObjects((_ACMEProviderDNS01ChallengeOptions)(o), v)
|
||||
}
|
||||
|
||||
func (o *ACMEProviderDNS01ChallengeOptions) UnmarshalJSON(bytes []byte) error {
|
||||
err := json.Unmarshal(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var v any
|
||||
switch o.Provider {
|
||||
case C.DNSProviderAliDNS:
|
||||
v = &o.AliDNSOptions
|
||||
case C.DNSProviderCloudflare:
|
||||
v = &o.CloudflareOptions
|
||||
case C.DNSProviderACMEDNS:
|
||||
v = &o.ACMEDNSOptions
|
||||
case "":
|
||||
return E.New("missing provider type")
|
||||
default:
|
||||
return E.New("unknown provider type: ", o.Provider)
|
||||
}
|
||||
return badjson.UnmarshallExcluded(bytes, (*_ACMEProviderDNS01ChallengeOptions)(o), v)
|
||||
}
|
||||
|
||||
type ACMEKeyType string
|
||||
|
||||
const (
|
||||
ACMEKeyTypeED25519 = ACMEKeyType("ed25519")
|
||||
ACMEKeyTypeP256 = ACMEKeyType("p256")
|
||||
ACMEKeyTypeP384 = ACMEKeyType("p384")
|
||||
ACMEKeyTypeRSA2048 = ACMEKeyType("rsa2048")
|
||||
ACMEKeyTypeRSA4096 = ACMEKeyType("rsa4096")
|
||||
)
|
||||
|
||||
func (t *ACMEKeyType) UnmarshalJSON(data []byte) error {
|
||||
var value string
|
||||
err := json.Unmarshal(data, &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value = strings.ToLower(value)
|
||||
switch ACMEKeyType(value) {
|
||||
case "", ACMEKeyTypeED25519, ACMEKeyTypeP256, ACMEKeyTypeP384, ACMEKeyTypeRSA2048, ACMEKeyTypeRSA4096:
|
||||
*t = ACMEKeyType(value)
|
||||
default:
|
||||
return E.New("unknown ACME key type: ", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
100
option/certificate_provider.go
Normal file
100
option/certificate_provider.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badjson"
|
||||
"github.com/sagernet/sing/service"
|
||||
)
|
||||
|
||||
type CertificateProviderOptionsRegistry interface {
|
||||
CreateOptions(providerType string) (any, bool)
|
||||
}
|
||||
|
||||
type _CertificateProvider struct {
|
||||
Type string `json:"type"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Options any `json:"-"`
|
||||
}
|
||||
|
||||
type CertificateProvider _CertificateProvider
|
||||
|
||||
func (h *CertificateProvider) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
||||
return badjson.MarshallObjectsContext(ctx, (*_CertificateProvider)(h), h.Options)
|
||||
}
|
||||
|
||||
func (h *CertificateProvider) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
||||
err := json.UnmarshalContext(ctx, content, (*_CertificateProvider)(h))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
|
||||
if registry == nil {
|
||||
return E.New("missing certificate provider options registry in context")
|
||||
}
|
||||
options, loaded := registry.CreateOptions(h.Type)
|
||||
if !loaded {
|
||||
return E.New("unknown certificate provider type: ", h.Type)
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, content, (*_CertificateProvider)(h), options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
h.Options = options
|
||||
return nil
|
||||
}
|
||||
|
||||
type CertificateProviderOptions struct {
|
||||
Tag string `json:"-"`
|
||||
Type string `json:"-"`
|
||||
Options any `json:"-"`
|
||||
}
|
||||
|
||||
type _CertificateProviderInline struct {
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
func (o *CertificateProviderOptions) MarshalJSONContext(ctx context.Context) ([]byte, error) {
|
||||
if o.Tag != "" {
|
||||
return json.Marshal(o.Tag)
|
||||
}
|
||||
return badjson.MarshallObjectsContext(ctx, _CertificateProviderInline{Type: o.Type}, o.Options)
|
||||
}
|
||||
|
||||
func (o *CertificateProviderOptions) UnmarshalJSONContext(ctx context.Context, content []byte) error {
|
||||
if len(content) == 0 {
|
||||
return E.New("empty certificate_provider value")
|
||||
}
|
||||
if content[0] == '"' {
|
||||
return json.UnmarshalContext(ctx, content, &o.Tag)
|
||||
}
|
||||
var inline _CertificateProviderInline
|
||||
err := json.UnmarshalContext(ctx, content, &inline)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Type = inline.Type
|
||||
if o.Type == "" {
|
||||
return E.New("missing certificate provider type")
|
||||
}
|
||||
registry := service.FromContext[CertificateProviderOptionsRegistry](ctx)
|
||||
if registry == nil {
|
||||
return E.New("missing certificate provider options registry in context")
|
||||
}
|
||||
options, loaded := registry.CreateOptions(o.Type)
|
||||
if !loaded {
|
||||
return E.New("unknown certificate provider type: ", o.Type)
|
||||
}
|
||||
err = badjson.UnmarshallExcludedContext(ctx, content, &inline, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.Options = options
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *CertificateProviderOptions) IsShared() bool {
|
||||
return o.Tag != ""
|
||||
}
|
||||
@@ -10,18 +10,19 @@ import (
|
||||
)
|
||||
|
||||
type _Options struct {
|
||||
RawMessage json.RawMessage `json:"-"`
|
||||
Schema string `json:"$schema,omitempty"`
|
||||
Log *LogOptions `json:"log,omitempty"`
|
||||
DNS *DNSOptions `json:"dns,omitempty"`
|
||||
NTP *NTPOptions `json:"ntp,omitempty"`
|
||||
Certificate *CertificateOptions `json:"certificate,omitempty"`
|
||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||
Route *RouteOptions `json:"route,omitempty"`
|
||||
Services []Service `json:"services,omitempty"`
|
||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||
RawMessage json.RawMessage `json:"-"`
|
||||
Schema string `json:"$schema,omitempty"`
|
||||
Log *LogOptions `json:"log,omitempty"`
|
||||
DNS *DNSOptions `json:"dns,omitempty"`
|
||||
NTP *NTPOptions `json:"ntp,omitempty"`
|
||||
Certificate *CertificateOptions `json:"certificate,omitempty"`
|
||||
CertificateProviders []CertificateProvider `json:"certificate_providers,omitempty"`
|
||||
Endpoints []Endpoint `json:"endpoints,omitempty"`
|
||||
Inbounds []Inbound `json:"inbounds,omitempty"`
|
||||
Outbounds []Outbound `json:"outbounds,omitempty"`
|
||||
Route *RouteOptions `json:"route,omitempty"`
|
||||
Services []Service `json:"services,omitempty"`
|
||||
Experimental *ExperimentalOptions `json:"experimental,omitempty"`
|
||||
}
|
||||
|
||||
type Options _Options
|
||||
@@ -56,6 +57,25 @@ func checkOptions(options *Options) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkCertificateProviders(options.CertificateProviders)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCertificateProviders(providers []CertificateProvider) error {
|
||||
seen := make(map[string]bool)
|
||||
for i, provider := range providers {
|
||||
tag := provider.Tag
|
||||
if tag == "" {
|
||||
tag = F.ToString(i)
|
||||
}
|
||||
if seen[tag] {
|
||||
return E.New("duplicate certificate provider tag: ", tag)
|
||||
}
|
||||
seen[tag] = true
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
76
option/origin_ca.go
Normal file
76
option/origin_ca.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package option
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/sing/common/json"
|
||||
"github.com/sagernet/sing/common/json/badoption"
|
||||
)
|
||||
|
||||
type CloudflareOriginCACertificateProviderOptions struct {
|
||||
Domain badoption.Listable[string] `json:"domain,omitempty"`
|
||||
DataDirectory string `json:"data_directory,omitempty"`
|
||||
APIToken string `json:"api_token,omitempty"`
|
||||
OriginCAKey string `json:"origin_ca_key,omitempty"`
|
||||
RequestType CloudflareOriginCARequestType `json:"request_type,omitempty"`
|
||||
RequestedValidity CloudflareOriginCARequestValidity `json:"requested_validity,omitempty"`
|
||||
Detour string `json:"detour,omitempty"`
|
||||
}
|
||||
|
||||
type CloudflareOriginCARequestType string
|
||||
|
||||
const (
|
||||
CloudflareOriginCARequestTypeOriginRSA = CloudflareOriginCARequestType("origin-rsa")
|
||||
CloudflareOriginCARequestTypeOriginECC = CloudflareOriginCARequestType("origin-ecc")
|
||||
)
|
||||
|
||||
func (t *CloudflareOriginCARequestType) UnmarshalJSON(data []byte) error {
|
||||
var value string
|
||||
err := json.Unmarshal(data, &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
value = strings.ToLower(value)
|
||||
switch CloudflareOriginCARequestType(value) {
|
||||
case "", CloudflareOriginCARequestTypeOriginRSA, CloudflareOriginCARequestTypeOriginECC:
|
||||
*t = CloudflareOriginCARequestType(value)
|
||||
default:
|
||||
return E.New("unsupported Cloudflare Origin CA request type: ", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type CloudflareOriginCARequestValidity uint16
|
||||
|
||||
const (
|
||||
CloudflareOriginCARequestValidity7 = CloudflareOriginCARequestValidity(7)
|
||||
CloudflareOriginCARequestValidity30 = CloudflareOriginCARequestValidity(30)
|
||||
CloudflareOriginCARequestValidity90 = CloudflareOriginCARequestValidity(90)
|
||||
CloudflareOriginCARequestValidity365 = CloudflareOriginCARequestValidity(365)
|
||||
CloudflareOriginCARequestValidity730 = CloudflareOriginCARequestValidity(730)
|
||||
CloudflareOriginCARequestValidity1095 = CloudflareOriginCARequestValidity(1095)
|
||||
CloudflareOriginCARequestValidity5475 = CloudflareOriginCARequestValidity(5475)
|
||||
)
|
||||
|
||||
func (v *CloudflareOriginCARequestValidity) UnmarshalJSON(data []byte) error {
|
||||
var value uint16
|
||||
err := json.Unmarshal(data, &value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
switch CloudflareOriginCARequestValidity(value) {
|
||||
case 0,
|
||||
CloudflareOriginCARequestValidity7,
|
||||
CloudflareOriginCARequestValidity30,
|
||||
CloudflareOriginCARequestValidity90,
|
||||
CloudflareOriginCARequestValidity365,
|
||||
CloudflareOriginCARequestValidity730,
|
||||
CloudflareOriginCARequestValidity1095,
|
||||
CloudflareOriginCARequestValidity5475:
|
||||
*v = CloudflareOriginCARequestValidity(value)
|
||||
default:
|
||||
return E.New("unsupported Cloudflare Origin CA requested validity: ", value)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -36,6 +36,10 @@ type TailscaleDNSServerOptions struct {
|
||||
AcceptDefaultResolvers bool `json:"accept_default_resolvers,omitempty"`
|
||||
}
|
||||
|
||||
type TailscaleCertificateProviderOptions struct {
|
||||
Endpoint string `json:"endpoint,omitempty"`
|
||||
}
|
||||
|
||||
type DERPServiceOptions struct {
|
||||
ListenOptions
|
||||
InboundTLSOptionsContainer
|
||||
|
||||
@@ -28,9 +28,13 @@ type InboundTLSOptions struct {
|
||||
KeyPath string `json:"key_path,omitempty"`
|
||||
KernelTx bool `json:"kernel_tx,omitempty"`
|
||||
KernelRx bool `json:"kernel_rx,omitempty"`
|
||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||
CertificateProvider *CertificateProviderOptions `json:"certificate_provider,omitempty"`
|
||||
|
||||
// Deprecated: use certificate_provider
|
||||
ACME *InboundACMEOptions `json:"acme,omitempty"`
|
||||
|
||||
ECH *InboundECHOptions `json:"ech,omitempty"`
|
||||
Reality *InboundRealityOptions `json:"reality,omitempty"`
|
||||
}
|
||||
|
||||
type ClientAuthType tls.ClientAuthType
|
||||
|
||||
Reference in New Issue
Block a user