Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

egctl create httpproxy cmd support update or create autocertmanager #1188

Merged
merged 3 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
127 changes: 122 additions & 5 deletions cmd/client/commandv2/create/createhttpproxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package create
import (
"encoding/base64"
"fmt"
"net/http"
"os"
"path/filepath"
"strings"
Expand All @@ -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"
Expand All @@ -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{}
Expand All @@ -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:https://127.0.0.1:8080" and "http:https://127.0.0.1:8081" and
Expand All @@ -76,6 +87,27 @@ egctl create httpproxy demo --port 10080 \
# with path prefix "foo.com/prefix" to "http:https://127.0.0.1:8083".
egctl create httpproxy demo2 --port 10081 \
--rule="foo.com/prefix*=http:https://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:https://127.0.0.1:8083" \
--auto-cert \
--auto-cert-email [email protected] \
--auto-cert-domain-name="*.foo.com" \
--dns-provider name=dnspod \
--dns-provider zone=megaease.com \
--dns-provider="apiToken=<tokenvalue>"

# Create HTTPServer, Pipelines with an existing AutoCertManager and update it.
egctl create httpproxy demo2 --port 10081 \
--rule="/bar=http:https://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=<tokenvalue>"
`

// HTTPProxyCmd returns create command of HTTPProxy.
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down
102 changes: 97 additions & 5 deletions cmd/client/commandv2/create/createhttpproxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -255,16 +255,28 @@ func TestCreateHTTPProxyOptions(t *testing.T) {
"foo.com/bar*=http:https://127.0.0.1:9095",
"/bar=http:https://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: "[email protected]",
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
Expand Down Expand Up @@ -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: "[email protected]",
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("[email protected]", 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": "[email protected]",
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("[email protected]", spec.Email)

assert.Equal(2, len(spec.Domains))
assert.Equal("*.easegress.org", spec.Domains[0].Name)
assert.Equal("*.easegress.example", spec.Domains[1].Name)
}
33 changes: 33 additions & 0 deletions cmd/client/commandv2/specs/spec.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}