Skip to content

Commit

Permalink
feat(autok3s): add proxy for UI
Browse files Browse the repository at this point in the history
  • Loading branch information
JacieChao committed May 25, 2022
1 parent 25c382b commit 68918fb
Show file tree
Hide file tree
Showing 11 changed files with 355 additions and 16 deletions.
11 changes: 10 additions & 1 deletion pkg/common/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,19 @@ var (
);`,
`CREATE TABLE IF NOT EXISTS explorers
(
context_name not null primary key,
context_name TEXT not null primary key,
enabled bool,
port int
);`,
`CREATE TABLE IF NOT EXISTS settings
(
name TEXT not null primary key,
value BLOB
);`,
`INSERT INTO settings(name,value)
SELECT 'whitelist-domain', ''
WHERE NOT EXISTS(SELECT 1 FROM settings WHERE name='whitelist-domain'
);`,
}
)

Expand Down
42 changes: 42 additions & 0 deletions pkg/common/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,12 @@ type Explorer struct {
Port int `json:"port"`
}

// Setting struct
type Setting struct {
Name string `json:"name"`
Value string `json:"value"`
}

type templateEvent struct {
Name string
Object *Template
Expand Down Expand Up @@ -635,3 +641,39 @@ func (d *Store) ListExplorer() ([]*Explorer, error) {
result := d.DB.Find(&list)
return list, result.Error
}

// GetSetting return specified setting by name
func (d *Store) GetSetting(name string) (*Setting, error) {
s := &Setting{}
result := d.DB.Where("name = ?", name).Find(s)
if result.Error != nil {
return nil, result.Error
}
if result.RowsAffected == 0 {
return nil, nil
}
return s, nil
}

// SaveSetting save settings
func (d *Store) SaveSetting(s *Setting) error {
e, err := d.GetSetting(s.Name)
if err != nil {
return err
}
if e != nil {
// update setting
result := d.DB.Where("name = ? ", s.Name).Omit("name").Save(s)
return result.Error
}
// save setting
result := d.DB.Create(s)
return result.Error
}

// ListSettings list all settings
func (d *Store) ListSettings() ([]*Setting, error) {
list := make([]*Setting, 0)
result := d.DB.Find(&list)
return list, result.Error
}
4 changes: 3 additions & 1 deletion pkg/providers/harvester/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"strings"

"github.com/cnrancher/autok3s/pkg/utils"

harvsterv1 "github.com/harvester/harvester/pkg/apis/harvesterhci.io/v1beta1"
harvclient "github.com/harvester/harvester/pkg/generated/clientset/versioned"
"github.com/harvester/harvester/pkg/generated/clientset/versioned/scheme"
Expand Down Expand Up @@ -82,7 +84,7 @@ func (h *Harvester) getRestConfig() (*rest.Config, error) {
if h.KubeConfigContent == "" {
return kubeconfig.GetNonInteractiveClientConfig(h.KubeConfigFile).ClientConfig()
}
return clientcmd.RESTConfigFromKubeConfig([]byte(stringSupportBase64(h.KubeConfigContent)))
return clientcmd.RESTConfigFromKubeConfig([]byte(utils.StringSupportBase64(h.KubeConfigContent)))
}

func (h *Harvester) getClient() (*Client, error) {
Expand Down
6 changes: 4 additions & 2 deletions pkg/providers/harvester/cloudinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"io/ioutil"

"github.com/cnrancher/autok3s/pkg/utils"

"github.com/ghodss/yaml"
"github.com/harvester/harvester/pkg/builder"
"github.com/imdario/mergo"
Expand Down Expand Up @@ -89,15 +91,15 @@ func (h *Harvester) mergeCloudInit() (string, string, error) {
userData = string(userDataByte)
}
if h.UserData != "" {
userDataByte, err := mergeYaml([]byte(userData), []byte(stringSupportBase64(h.UserData)))
userDataByte, err := mergeYaml([]byte(userData), []byte(utils.StringSupportBase64(h.UserData)))
if err != nil {
return "", "", err
}
userData = string(userDataByte)
}
userData = userDataHeader + userData
if h.NetworkData != "" {
networkData = stringSupportBase64(h.NetworkData)
networkData = utils.StringSupportBase64(h.NetworkData)
}
return userData, networkData, nil
}
Expand Down
12 changes: 0 additions & 12 deletions pkg/providers/harvester/harvester.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package harvester

import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"io/ioutil"
Expand Down Expand Up @@ -725,14 +724,3 @@ func getStateFormVMI(vmi *kubevirtv1.VirtualMachineInstance) string {
return "None"
}
}

func stringSupportBase64(value string) string {
if value == "" {
return value
}
valueByte, err := base64.StdEncoding.DecodeString(value)
if err != nil {
valueByte = []byte(value)
}
return string(valueByte)
}
146 changes: 146 additions & 0 deletions pkg/server/proxy/http_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package proxy

import (
"crypto/tls"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"os"
"regexp"
"strings"

"github.com/cnrancher/autok3s/pkg/common"

"github.com/sirupsen/logrus"
utilnet "k8s.io/apimachinery/pkg/util/net"
)

const (
forwardProto = "X-Forwarded-Proto"
whiteListDomainSettingName = "whitelist-domain"
hostRegex = "[A-Za-z0-9-]+"
)

var (
httpStart = regexp.MustCompile("^http:/([^/])")
httpsStart = regexp.MustCompile("^https:/([^/])")
)

type proxy struct {
prefix string
}

// NewProxy return http proxy handler
func NewProxy(prefix string) http.Handler {
p := &proxy{
prefix: prefix,
}
return &httputil.ReverseProxy{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
Director: func(req *http.Request) {
if err := p.proxy(req); err != nil {
logrus.Infof("Failed to proxy: %v", err)
}
},
}
}

func (p *proxy) isAllowed(host string) bool {
setting, err := common.DefaultDB.GetSetting(whiteListDomainSettingName)
if err != nil || setting == nil {
logrus.Errorf("failed to get whitelist-domain setting, err %v", err)
return false
}
validHosts := strings.Split(setting.Value, ",")
for _, valid := range validHosts {
if valid == host {
return true
}

if strings.HasPrefix(valid, "*") && strings.HasSuffix(host, valid[1:]) {
return true
}

if strings.Contains(valid, ".%.") || strings.HasPrefix(valid, "%.") {
r := constructRegex(valid)
if match := r.MatchString(host); match {
return true
}
}
}
return false
}

func (p *proxy) proxy(req *http.Request) error {
path := req.URL.String()
index := strings.Index(path, p.prefix)
destPath := path[index+len(p.prefix):]
destPath, err := unescapePath(destPath)
if err != nil {
return err
}

if httpsStart.MatchString(destPath) {
destPath = httpsStart.ReplaceAllString(destPath, "https://$1")
} else if httpStart.MatchString(destPath) {
destPath = httpStart.ReplaceAllString(destPath, "http://$1")
} else {
destPath = "https://" + destPath
}

destURL, err := url.Parse(destPath)
if err != nil {
return err
}

destURL.RawQuery = req.URL.RawQuery
destURLHostname := destURL.Hostname()

if !p.isAllowed(destURLHostname) {
return fmt.Errorf("invalid host: %v", destURLHostname)
}

headerCopy := utilnet.CloneHeader(req.Header)
if req.TLS != nil {
headerCopy.Set(forwardProto, "https")
}
req.Host = destURLHostname
req.URL = destURL
req.Header = headerCopy

return nil
}

func constructRegex(host string) *regexp.Regexp {
// incoming host "ec2.%.amazonaws.com"
// Converted to regex "^ec2\.[A-Za-z0-9-]+\.amazonaws\.com$"
parts := strings.Split(host, ".")
for i, part := range parts {
if part == "%" {
parts[i] = hostRegex
} else {
parts[i] = regexp.QuoteMeta(part)
}
}

str := "^" + strings.Join(parts, "\\.") + "$"

return regexp.MustCompile(str)
}

func unescapePath(destPath string) (string, error) {
var err error
if os.Getenv("AUTOK3S_DEV_MODE") != "" {
destPath, err = url.QueryUnescape(destPath)
logrus.Infof("******* proxy url: %v ********", destPath)
if err != nil {
return "", err
}
}
return destPath, nil
}
60 changes: 60 additions & 0 deletions pkg/server/proxy/k8s_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package proxy

import (
"net/http"
"net/url"
"strings"

"github.com/cnrancher/autok3s/pkg/utils"

"github.com/sirupsen/logrus"
k8sproxy "k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
)

type K8sProxyHandler struct{}

func NewK8sProxy() http.Handler {
return &K8sProxyHandler{}
}

type errorResponder struct{}

func (r *errorResponder) Error(w http.ResponseWriter, req *http.Request, err error) {
logrus.Errorf("Error while proxying request: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}

func (kh *K8sProxyHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
config := req.Header.Get("config")
cfg, err := clientcmd.RESTConfigFromKubeConfig([]byte(utils.StringSupportBase64(config)))
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
rw.Write([]byte(err.Error()))
return
}
host := cfg.Host
if !strings.HasSuffix(host, "/") {
host = host + "/"
}
u, err := url.Parse(host)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
rw.Write([]byte(err.Error()))
return
}

u.Path = strings.TrimPrefix(req.URL.Path, "/k8s/proxy")
u.RawQuery = req.URL.RawQuery
req.URL.Host = req.Host
responder := &errorResponder{}
transport, err := rest.TransportFor(cfg)
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
rw.Write([]byte(err.Error()))
return
}
handler := k8sproxy.NewUpgradeAwareHandler(u, transport, true, false, responder)
handler.ServeHTTP(rw, req)
}
9 changes: 9 additions & 0 deletions pkg/server/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/cnrancher/autok3s/pkg/server/store/explorer"
"github.com/cnrancher/autok3s/pkg/server/store/kubectl"
"github.com/cnrancher/autok3s/pkg/server/store/provider"
"github.com/cnrancher/autok3s/pkg/server/store/settings"
"github.com/cnrancher/autok3s/pkg/server/store/template"
"github.com/cnrancher/autok3s/pkg/server/store/websocket"
wkube "github.com/cnrancher/autok3s/pkg/server/store/websocket/kubectl"
Expand Down Expand Up @@ -106,3 +107,11 @@ func initExplorer(s *types.APISchemas) {
schema.ResourceMethods = []string{http.MethodGet}
})
}

func initSettings(s *types.APISchemas) {
s.MustImportAndCustomize(common.Setting{}, func(schema *types.APISchema) {
schema.Store = &settings.Store{}
schema.CollectionMethods = []string{http.MethodGet}
schema.ResourceMethods = []string{http.MethodPut, http.MethodGet}
})
}
3 changes: 3 additions & 0 deletions pkg/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func Start() http.Handler {
initLogs(s.Schemas)
initTemplates(s.Schemas)
initExplorer(s.Schemas)
initSettings(s.Schemas)

apiroot.Register(s.Schemas, []string{"v1"})
router := mux.NewRouter()
Expand Down Expand Up @@ -65,6 +66,8 @@ func Start() http.Handler {
router.Handle("/debug/pprof/mutex", pprof.Handler("mutex"))

router.PathPrefix("/proxy/explorer/{name}").Handler(proxy.NewExplorerProxy())
router.PathPrefix("/meta/proxy").Handler(proxy.NewProxy("/proxy/"))
router.PathPrefix("/k8s/proxy").Handler(proxy.NewK8sProxy())
router.Path("/{prefix}/{type}").Handler(s)
router.Path("/{prefix}/{type}/{name}").Queries("link", "{link}").Handler(s)
router.Path("/{prefix}/{type}/{name}").Queries("action", "{action}").Handler(s)
Expand Down
Loading

0 comments on commit 68918fb

Please sign in to comment.