diff --git a/cmd/client/commandv2/create/createhttpproxy.go b/cmd/client/commandv2/create/createhttpproxy.go index 5f273ce6fe..091861fbc8 100644 --- a/cmd/client/commandv2/create/createhttpproxy.go +++ b/cmd/client/commandv2/create/createhttpproxy.go @@ -20,6 +20,7 @@ package create import ( "encoding/base64" "fmt" + "net/http" "os" "path/filepath" "strings" @@ -30,6 +31,7 @@ import ( "github.com/megaease/easegress/v2/pkg/filters" "github.com/megaease/easegress/v2/pkg/filters/proxies" "github.com/megaease/easegress/v2/pkg/filters/proxies/httpproxy" + "github.com/megaease/easegress/v2/pkg/object/autocertmanager" "github.com/megaease/easegress/v2/pkg/object/httpserver/routers" "github.com/megaease/easegress/v2/pkg/util/codectool" "github.com/spf13/cobra" @@ -47,10 +49,15 @@ type HTTPProxyOptions struct { CertFiles []string KeyFiles []string - caCert string - certs []string - keys []string - rules []*HTTPProxyRule + AutoCertDomainName string + AutoCertEmail string + AutoCertDNSProvider []string + + caCert string + certs []string + keys []string + rules []*HTTPProxyRule + dnsProvider map[string]string } var httpProxyOptions = &HTTPProxyOptions{} @@ -63,7 +70,11 @@ egctl create httpproxy NAME --port PORT \ [--auto-cert] \ [--ca-cert-file CA_CERT_FILE] \ [--cert-file CERT_FILE] \ - [--key-file KEY_FILE] + [--key-file KEY_FILE] \ + [--auto-cert-email EMAIL] \ + [--auto-cert-domain-name DOMAIN_NAME] \ + [--dns-provider KEY=VALUE] \ + [--dns-provider KEY2=VALUE2] # Create a HTTPServer (with port 10080) and corresponding Pipelines to direct # request with path "/bar" to "http://127.0.0.1:8080" and "http://127.0.0.1:8081" and @@ -76,6 +87,27 @@ egctl create httpproxy demo --port 10080 \ # with path prefix "foo.com/prefix" to "http://127.0.0.1:8083". egctl create httpproxy demo2 --port 10081 \ --rule="foo.com/prefix*=http://127.0.0.1:8083" + +# Create HTTPServer, Pipelines with a new AutoCertManager. +# auto-cert-email is required for creating a new AutoCertManager. +# If an AutoCertManager exists, this updates its email field. +egctl create httpproxy demo2 --port 10081 \ + --rule="/bar=http://127.0.0.1:8083" \ + --auto-cert \ + --auto-cert-email someone@megaease.com \ + --auto-cert-domain-name="*.foo.com" \ + --dns-provider name=dnspod \ + --dns-provider zone=megaease.com \ + --dns-provider="apiToken=" + +# Create HTTPServer, Pipelines with an existing AutoCertManager and update it. +egctl create httpproxy demo2 --port 10081 \ + --rule="/bar=http://127.0.0.1:8083" \ + --auto-cert \ + --auto-cert-domain-name="*.foo.com" \ + --dns-provider name=dnspod \ + --dns-provider zone=megaease.com \ + --dns-provider="apiToken=" ` // HTTPProxyCmd returns create command of HTTPProxy. @@ -103,6 +135,9 @@ func HTTPProxyCmd() *cobra.Command { cmd.Flags().StringVar(&o.CaCertFile, "ca-cert-file", "", "CA cert file") cmd.Flags().StringArrayVar(&o.CertFiles, "cert-file", []string{}, "Cert file") cmd.Flags().StringArrayVar(&o.KeyFiles, "key-file", []string{}, "Key file") + cmd.Flags().StringVar(&o.AutoCertDomainName, "auto-cert-domain-name", "", "Auto cert domain name") + cmd.Flags().StringArrayVar(&o.AutoCertDNSProvider, "dns-provider", []string{}, "Auto cert DNS provider") + cmd.Flags().StringVar(&o.AutoCertEmail, "auto-cert-email", "", "Auto cert email") return cmd } @@ -134,6 +169,20 @@ func httpProxyRun(cmd *cobra.Command, args []string) error { for _, p := range pls { allSpec = append(allSpec, p) } + if o.AutoCertDomainName != "" { + autoCertSpec, err := o.TranslateAutoCertManager() + if err != nil { + return err + } + generalSpec, err := toGeneralSpec(autoCertSpec) + if err != nil { + return err + } + err = resources.ApplyObject(cmd, generalSpec) + if err != nil { + return err + } + } for _, s := range allSpec { spec, err := toGeneralSpec(s) if err != nil { @@ -194,6 +243,27 @@ func (o *HTTPProxyOptions) Parse() error { keys = append(keys, key) } o.keys = keys + + // parse dns provider + if o.AutoCertDomainName != "" || len(o.AutoCertDNSProvider) != 0 { + if !o.AutoCert { + return fmt.Errorf("auto cert domain name or dns provider is set, but auto cert is not enabled") + } + if o.AutoCertDomainName == "" { + return fmt.Errorf("auto cert domain name is required when provide dns provider") + } + if len(o.AutoCertDNSProvider) == 0 { + return fmt.Errorf("auto cert dns provider is required when provide auto cert domain name") + } + } + o.dnsProvider = map[string]string{} + for _, dnsProvider := range o.AutoCertDNSProvider { + parts := strings.SplitN(dnsProvider, "=", 2) + if len(parts) != 2 { + return fmt.Errorf("dns provider %s should in format 'name=secret', invalid format", dnsProvider) + } + o.dnsProvider[parts[0]] = parts[1] + } return nil } @@ -258,6 +328,53 @@ func (o *HTTPProxyOptions) translateRules() (routers.Rules, []*specs.PipelineSpe return rules, pipelines } +var handleReqHook = general.HandleRequest + +// TranslateAutoCertManager translates AutoCertManagerSpec. +func (o *HTTPProxyOptions) TranslateAutoCertManager() (*specs.AutoCertManagerSpec, error) { + url := general.MakePath(general.ObjectsURL) + body, err := handleReqHook(http.MethodGet, url, nil) + if err != nil { + return nil, err + } + allSpecs, err := general.UnmarshalMapInterface(body, true) + if err != nil { + return nil, err + } + var spec *specs.AutoCertManagerSpec + for _, s := range allSpecs { + if s["kind"] == "AutoCertManager" { + if spec == nil { + spec = &specs.AutoCertManagerSpec{} + data, err := codectool.MarshalYAML(s) + if err != nil { + return nil, err + } + if err := codectool.Unmarshal(data, spec); err != nil { + return nil, err + } + } else { + return nil, fmt.Errorf("there are more than one AutoCertManager") + } + } + } + if spec == nil { + if o.AutoCertEmail != "" { + spec = specs.NewAutoCertManagerSpec() + spec.Email = o.AutoCertEmail + } else { + return nil, fmt.Errorf("there is no AutoCertManager and auto-cert-email is not set, please create one or set auto-cert-email") + } + } else if o.AutoCertEmail != "" { + spec.Email = o.AutoCertEmail + } + spec.AddOrUpdateDomain(&autocertmanager.DomainSpec{ + Name: o.AutoCertDomainName, + DNSProvider: o.dnsProvider, + }) + return spec, nil +} + func toGeneralSpec(data interface{}) (*general.Spec, error) { var yamlStr []byte var err error diff --git a/cmd/client/commandv2/create/createhttpproxy_test.go b/cmd/client/commandv2/create/createhttpproxy_test.go index a8aec49c6a..b20bfdfdcd 100644 --- a/cmd/client/commandv2/create/createhttpproxy_test.go +++ b/cmd/client/commandv2/create/createhttpproxy_test.go @@ -255,16 +255,28 @@ func TestCreateHTTPProxyOptions(t *testing.T) { "foo.com/bar*=http://127.0.0.1:9095", "/bar=http://127.0.0.1:9095", }, - TLS: true, - AutoCert: true, - CaCertFile: createCert("ca.cert"), - CertFiles: []string{createCert("cert1"), createCert("cert2")}, - KeyFiles: []string{createCert("key1"), createCert("key2")}, + TLS: true, + AutoCert: true, + CaCertFile: createCert("ca.cert"), + CertFiles: []string{createCert("cert1"), createCert("cert2")}, + KeyFiles: []string{createCert("key1"), createCert("key2")}, + AutoCertEmail: "someone@easegress.com", + AutoCertDomainName: "*.easegress.example", + AutoCertDNSProvider: []string{ + "name=dnspod", + "zone=easegress.com", + "apiToken=abc", + }, } o.Complete([]string{"test"}) err = o.Parse() assert.Nil(err) + // auto cert + assert.Equal("dnspod", o.dnsProvider["name"]) + assert.Equal("easegress.com", o.dnsProvider["zone"]) + assert.Equal("abc", o.dnsProvider["apiToken"]) + hs, pls := o.Translate() // meta @@ -383,3 +395,83 @@ func TestCreateHTTPProxyCmd(t *testing.T) { err = httpProxyRun(cmd, []string{"demo"}) assert.NotNil(t, err) } + +func TestTranslateAutoCertManager(t *testing.T) { + assert := assert.New(t) + + originalHook := handleReqHook + defer func() { + handleReqHook = originalHook + }() + handleReqHook = func(httpMethod string, path string, yamlBody []byte) ([]byte, error) { + return []byte("[]"), nil + } + option := &HTTPProxyOptions{ + AutoCert: true, + AutoCertEmail: "some@easegress.com", + AutoCertDomainName: "*.easegress.example", + AutoCertDNSProvider: []string{ + "name=dnspod", + "zone=easegress.com", + "apiToken=abc", + }, + } + option.Complete([]string{"test"}) + err := option.Parse() + assert.Nil(err) + + spec, err := option.TranslateAutoCertManager() + assert.Nil(err) + assert.Equal("AutoCertManager", spec.Kind) + assert.Equal("autocertmanager", spec.Name) + assert.Equal("some@easegress.com", spec.Email) + assert.Equal(1, len(spec.Domains)) + assert.Equal("*.easegress.example", spec.Domains[0].Name) + assert.Equal(map[string]string{ + "name": "dnspod", + "zone": "easegress.com", + "apiToken": "abc", + }, spec.Domains[0].DNSProvider) + + handleReqHook = func(httpMethod string, path string, yamlBody []byte) ([]byte, error) { + return []byte(`[ + { + "kind": "AutoCertManager", + "name": "autocert", + "email": "anybody@easegress.com", + domains: [ + { + "name": "*.easegress.org", + "dnsProvider": { + "name": "dnspod", + "zone": "easegress.org", + "apiToken": "abc" + } + } + ] + } + ]`), nil + } + option = &HTTPProxyOptions{ + AutoCert: true, + AutoCertDomainName: "*.easegress.example", + AutoCertDNSProvider: []string{ + "name=aliyun", + "zone=easegress.com", + "apiToken=abc", + }, + } + option.Complete([]string{"test"}) + err = option.Parse() + assert.Nil(err) + + spec, err = option.TranslateAutoCertManager() + assert.Nil(err) + assert.Equal("AutoCertManager", spec.Kind) + assert.Equal("autocert", spec.Name) + assert.Equal("anybody@easegress.com", spec.Email) + + assert.Equal(2, len(spec.Domains)) + assert.Equal("*.easegress.org", spec.Domains[0].Name) + assert.Equal("*.easegress.example", spec.Domains[1].Name) +} diff --git a/cmd/client/commandv2/specs/spec.go b/cmd/client/commandv2/specs/spec.go index f4d91ab32c..5d54d799a8 100644 --- a/cmd/client/commandv2/specs/spec.go +++ b/cmd/client/commandv2/specs/spec.go @@ -22,6 +22,7 @@ import ( "github.com/megaease/easegress/v2/pkg/filters" "github.com/megaease/easegress/v2/pkg/filters/builder" "github.com/megaease/easegress/v2/pkg/filters/proxies/httpproxy" + "github.com/megaease/easegress/v2/pkg/object/autocertmanager" "github.com/megaease/easegress/v2/pkg/object/httpserver" "github.com/megaease/easegress/v2/pkg/object/pipeline" "github.com/megaease/easegress/v2/pkg/util/codectool" @@ -79,6 +80,10 @@ func getDefaultPipelineSpec() *pipeline.Spec { return (&pipeline.Pipeline{}).DefaultSpec().(*pipeline.Spec) } +func getDefaultAutoCertManagerSpec() *autocertmanager.Spec { + return (&autocertmanager.AutoCertManager{}).DefaultSpec().(*autocertmanager.Spec) +} + // NewProxyFilterSpec returns a new ProxyFilterSpec. func NewProxyFilterSpec(name string) *httpproxy.Spec { spec := GetDefaultFilterSpec(httpproxy.Kind).(*httpproxy.Spec) @@ -107,3 +112,31 @@ func NewRequestAdaptorFilterSpec(name string) *builder.RequestAdaptorSpec { func GetDefaultFilterSpec(kind string) filters.Spec { return filters.GetKind(kind).DefaultSpec() } + +// PipelineSpec is the spec of Pipeline. +type AutoCertManagerSpec struct { + Name string `json:"name"` + Kind string `json:"kind"` + + autocertmanager.Spec `json:",inline"` +} + +// NewAutoCertManagerSpec returns a new AutoCertManagerSpec. +func NewAutoCertManagerSpec() *AutoCertManagerSpec { + return &AutoCertManagerSpec{ + Name: "autocertmanager", + Kind: autocertmanager.Kind, + Spec: *getDefaultAutoCertManagerSpec(), + } +} + +// AddOrUpdateDomain adds or updates a domain. +func (a *AutoCertManagerSpec) AddOrUpdateDomain(domain *autocertmanager.DomainSpec) { + for i, d := range a.Domains { + if d.Name == domain.Name { + a.Domains[i] = *domain + return + } + } + a.Domains = append(a.Domains, *domain) +} diff --git a/docs/02.Tutorials/2.1.egctl-Usage.md b/docs/02.Tutorials/2.1.egctl-Usage.md index 96e8d1e45d..b03294cde9 100644 --- a/docs/02.Tutorials/2.1.egctl-Usage.md +++ b/docs/02.Tutorials/2.1.egctl-Usage.md @@ -38,7 +38,11 @@ egctl create httpproxy NAME --port PORT \ [--auto-cert] \ [--ca-cert-file CA_CERT_FILE] \ [--cert-file CERT_FILE] \ - [--key-file KEY_FILE] + [--key-file KEY_FILE] \ + [--auto-cert-email EMAIL] \ + [--auto-cert-domain-name DOMAIN_NAME] \ + [--dns-provider KEY=VALUE] \ + [--dns-provider KEY2=VALUE2] ``` For example: @@ -91,6 +95,39 @@ filters: policy: roundRobin ``` +You can use the `egctl create httpproxy` command to create or update an `AutoCertManager`. Below is an example command and its equivalent YAML configuration. + +For example: + +```bash +egctl create httpproxy demo2 --port 10081 \ + --rule="/bar=http://127.0.0.1:8083" \ + --auto-cert \ + --auto-cert-email someone@megaease.com \ + --auto-cert-domain-name="*.foo.com" \ + --dns-provider name=dnspod \ + --dns-provider zone=megaease.com \ + --dns-provider="apiToken=" +``` +Parameters: + +- `auto-cert-email`: Required for creating a new `AutoCertManager`. If an `AutoCertManager` exists, this updates its email field. +- `auto-cert-domain-name` and `dns-provider`: Specify the domain in the `AutoCertManager` config. This either appends to or updates the domains field of `AutoCertManager`. + +Equivalent YAML Configuration + +```yaml +kind: AutoCertManager +name: AutoCertManager +email: someone@megaease.com +domains: + - name: "*.megaease.com" + dnsProvider: + name: dnspod + zone: megaease.com + apiToken: +``` + ## Viewing and finding resources ```bash