From 575ea95525aea075b1ae8b542151185ee4ba14c5 Mon Sep 17 00:00:00 2001 From: Qiu Jian Date: Wed, 17 Oct 2018 16:45:11 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=B9=E8=BF=9B=EF=BC=9A=E5=85=81=E8=AE=B8cl?= =?UTF-8?q?imc=E6=8C=87=E5=AE=9Atls=E8=AF=81=E4=B9=A6=EF=BC=8C=E5=85=81?= =?UTF-8?q?=E8=AE=B8=E6=9C=8D=E5=8A=A1=E6=8C=87=E5=AE=9Atls=E8=AF=81?= =?UTF-8?q?=E4=B9=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/climc.go | 18 ++++++--- pkg/appsrv/appsrv.go | 15 +++++++- pkg/cloudcommon/app.go | 12 +++++- pkg/cloudcommon/auth.go | 2 +- pkg/cloudcommon/options.go | 4 ++ pkg/mcclient/auth/auth.go | 8 ++-- pkg/mcclient/mcclient.go | 24 ++++++++++-- pkg/util/seclib2/tls.go | 74 ++++++++++++++++++++++++++++++++++++ pkg/util/seclib2/tls_test.go | 19 +++++++++ 9 files changed, 159 insertions(+), 17 deletions(-) create mode 100644 pkg/util/seclib2/tls.go create mode 100644 pkg/util/seclib2/tls_test.go diff --git a/cmd/climc/climc.go b/cmd/climc/climc.go index f373f391cf..c2ef7dd63e 100644 --- a/cmd/climc/climc.go +++ b/cmd/climc/climc.go @@ -23,11 +23,15 @@ import ( ) type BaseOptions struct { - Help bool `help:"Show help" short-token:"h"` - Debug bool `help:"Show debug information"` - Version bool `help:"Show version"` - Timeout int `default:"600" help:"Number of seconds to wait for a response"` - Insecure bool `default:"false" help:"Allow skip server cert verification if URL is https" short-token:"k"` + Help bool `help:"Show help" short-token:"h"` + Debug bool `help:"Show debug information"` + Version bool `help:"Show version"` + Timeout int `default:"600" help:"Number of seconds to wait for a response"` + Insecure bool `default:"false" help:"Allow skip server cert verification if URL is https" short-token:"k"` + + CertFile string `default:"$YUNION_CERT_FILE" help:"certificate file"` + KeyFile string `default:"$YUNION_KEY_FILE" help:"private key file"` + UseCachedToken bool `default:"$YUNION_USE_CACHED_TOKEN|false" help:"Use cached token"` OsUsername string `default:"$OS_USERNAME" help:"Username, defaults to env[OS_USERNAME]"` OsPassword string `default:"$OS_PASSWORD" help:"Password, defaults to env[OS_PASSWORD]"` @@ -124,7 +128,9 @@ func newClientSession(options *BaseOptions) (*mcclient.ClientSession, error) { client := mcclient.NewClient(options.OsAuthURL, options.Timeout, options.Debug, - options.Insecure) + options.Insecure, + options.CertFile, + options.KeyFile) var cacheToken mcclient.TokenCredential authUrlAlter := strings.Replace(options.OsAuthURL, "/", "", -1) diff --git a/pkg/appsrv/appsrv.go b/pkg/appsrv/appsrv.go index e37f5db8b1..314bfc185f 100644 --- a/pkg/appsrv/appsrv.go +++ b/pkg/appsrv/appsrv.go @@ -309,7 +309,7 @@ func timeoutHandle(h http.Handler) http.HandlerFunc { } } -func (app *Application) ListenAndServe(addr string) { +func (app *Application) initServer(addr string) *http.Server { db := AppContextDB(app.context) if db != nil { db.SetMaxIdleConns(app.connMax + 1) @@ -325,8 +325,21 @@ func (app *Application) ListenAndServe(addr string) { WriteTimeout: app.writeTimeout, MaxHeaderBytes: 1 << 20, } + return s +} + +func (app *Application) ListenAndServe(addr string) { + s := app.initServer(addr) err := s.ListenAndServe() if err != nil { log.Fatalf("ListAndServer fail: %s", err) } } + +func (app *Application) ListenAndServeTLS(addr string, certFile, keyFile string) { + s := app.initServer(addr) + err := s.ListenAndServeTLS(certFile, keyFile) + if err != nil && err != http.ErrServerClosed { + log.Fatalf("ListAndServer fail: %s", err) + } +} diff --git a/pkg/cloudcommon/app.go b/pkg/cloudcommon/app.go index c2cde3c76b..c8c6523caf 100644 --- a/pkg/cloudcommon/app.go +++ b/pkg/cloudcommon/app.go @@ -22,6 +22,14 @@ func InitApp(options *Options) *appsrv.Application { func ServeForever(app *appsrv.Application, options *Options) { addr := net.JoinHostPort(options.Address, strconv.Itoa(options.Port)) - log.Infof("Start listen on %s", addr) - app.ListenAndServe(addr) + proto := "http" + if options.EnableSsl { + proto = "https" + } + log.Infof("Start listen on %s://%s", proto, addr) + if options.EnableSsl { + app.ListenAndServeTLS(addr, options.SslCertfile, options.SslKeyfile) + } else { + app.ListenAndServe(addr) + } } diff --git a/pkg/cloudcommon/auth.go b/pkg/cloudcommon/auth.go index b32b923bb7..6906dcb5db 100644 --- a/pkg/cloudcommon/auth.go +++ b/pkg/cloudcommon/auth.go @@ -36,7 +36,7 @@ func InitAuth(options *Options, authComplete auth.AuthCompletedCallback) { // debug := options.LogLevel == "debug" - auth.Init(a, false, true) // , authComplete) + auth.Init(a, false, true, options.SslCertfile, options.SslKeyfile) // , authComplete) authComplete() } diff --git a/pkg/cloudcommon/options.go b/pkg/cloudcommon/options.go index 5d97bf2f0f..39b0141f6c 100644 --- a/pkg/cloudcommon/options.go +++ b/pkg/cloudcommon/options.go @@ -36,6 +36,10 @@ type Options struct { GlobalVirtualResourceNamespace bool `help:"Per project namespace or global namespace for virtual resources"` DebugSqlchemy bool `default:"False" help:"Print SQL executed by sqlchemy"` + EnableSsl bool `help:"Enable https"` + SslCertfile string `help:"ssl certification file"` + SslKeyfile string `help:"ssl certification key file"` + structarg.BaseOptions } diff --git a/pkg/mcclient/auth/auth.go b/pkg/mcclient/auth/auth.go index ab37809305..3712bfd378 100644 --- a/pkg/mcclient/auth/auth.go +++ b/pkg/mcclient/auth/auth.go @@ -239,8 +239,8 @@ func (callback *AuthCompletedCallback) Run() { } } -func AsyncInit(info *AuthInfo, debug, insecure bool, callback AuthCompletedCallback) { - cli := mcclient.NewClient(info.AuthUrl, defaultTimeout, debug, insecure) +func AsyncInit(info *AuthInfo, debug, insecure bool, certFile, keyFile string, callback AuthCompletedCallback) { + cli := mcclient.NewClient(info.AuthUrl, defaultTimeout, debug, insecure, certFile, keyFile) manager = newAuthManager(cli, info) go manager.init() if callback != nil { @@ -248,12 +248,12 @@ func AsyncInit(info *AuthInfo, debug, insecure bool, callback AuthCompletedCallb } } -func Init(info *AuthInfo, debug, insecure bool) { +func Init(info *AuthInfo, debug, insecure bool, certFile, keyFile string) { done := make(chan bool, 1) f := func() { done <- true } - AsyncInit(info, debug, insecure, f) + AsyncInit(info, debug, insecure, certFile, keyFile, f) <-done } diff --git a/pkg/mcclient/mcclient.go b/pkg/mcclient/mcclient.go index a5c6d1907e..5142ae5677 100644 --- a/pkg/mcclient/mcclient.go +++ b/pkg/mcclient/mcclient.go @@ -11,6 +11,8 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/util/httputils" + "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/yunioncloud/pkg/gotypes" ) type Client struct { @@ -22,10 +24,26 @@ type Client struct { serviceCatalog IServiceCatalog } -func NewClient(authUrl string, timeout int, debug bool, insecure bool) *Client { - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, +func NewClient(authUrl string, timeout int, debug bool, insecure bool, certFile, keyFile string) *Client { + var tlsConf *tls.Config + + if len(certFile) > 0 && len(keyFile) > 0 { + var err error + tlsConf, err = seclib2.InitTLSConfig(certFile, keyFile) + if err != nil { + log.Errorf("load TLS failed %s", err) + } } + + if tlsConf == nil || gotypes.IsNil(tlsConf) { + tlsConf = &tls.Config{} + } + tlsConf.InsecureSkipVerify = insecure + + tr := &http.Transport{ + TLSClientConfig: tlsConf, + } + client := Client{authUrl: authUrl, timeout: timeout, debug: debug, diff --git a/pkg/util/seclib2/tls.go b/pkg/util/seclib2/tls.go new file mode 100644 index 0000000000..e7567baf6f --- /dev/null +++ b/pkg/util/seclib2/tls.go @@ -0,0 +1,74 @@ +package seclib2 + +import ( + "io/ioutil" + "crypto/tls" + "crypto/x509" + "bytes" + + "yunion.io/x/log" +) + +var CERT_SEP = []byte("-END CERTIFICATE-") + +func findCertEndIndex(certBytes []byte) int { + endpos := bytes.Index(certBytes, CERT_SEP) + if endpos < 0 { + return endpos + } + endpos += len(CERT_SEP) + for endpos < len(certBytes) && certBytes[endpos] != '\n' { + endpos += 1 + } + return endpos +} + +func splitCert(certBytes []byte) [][]byte { + ret := make([][]byte, 0) + for { + endpos := findCertEndIndex(certBytes) + if endpos > 0 { + ret = append(ret, certBytes[:endpos]) + for endpos < len(certBytes) && certBytes[endpos] != '-' { + endpos += 1 + } + if endpos < len(certBytes) { + certBytes = certBytes[endpos:] + } else { + break + } + } + } + return ret +} + +func InitTLSConfig(certFile, keyFile string) (*tls.Config, error) { + allCertPEM, err := ioutil.ReadFile(certFile) + if err != nil { + log.Errorf("read tls certfile fail %s", err) + return nil, err + } + certPEMs := splitCert(allCertPEM) + keyPEM, err := ioutil.ReadFile(keyFile) + if err != nil { + log.Errorf("read tls keyfile fail %s", err) + return nil, err + } + cert, err := tls.X509KeyPair(certPEMs[0], keyPEM) + if err != nil { + return nil, err + } + + caCertPool := x509.NewCertPool() + for i := 1; i < len(certPEMs); i += 1 { + caCertPool.AppendCertsFromPEM(certPEMs[i]) + } + + tlsConfig := &tls.Config{ + Certificates: []tls.Certificate{cert}, + RootCAs: caCertPool, + } + // tlsConfig.ServerName = "CN=*" + tlsConfig.BuildNameToCertificate() + return tlsConfig, nil +} diff --git a/pkg/util/seclib2/tls_test.go b/pkg/util/seclib2/tls_test.go new file mode 100644 index 0000000000..24722d43a6 --- /dev/null +++ b/pkg/util/seclib2/tls_test.go @@ -0,0 +1,19 @@ +package seclib2 + +import "testing" + +func TestSplitCert(t *testing.T) { + PEM := `-----BEGIN CERTIFICATE----- +MIIFADCCA+igAwIBAgIRAOMlOS6MEmLdT29AN1e8XfgwDQYJKoZIhvcNAQELBQAw +6vetSmRT35g6Tf/bZyPtPLnBOw4bpZtN/9KWJ5pJtKN80hgc +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIE2jCCA8KgAwIBAgIJAJlb8KChCsV+MA0GCSqGSIb3DQEBCwUAMIGfMQswCQYD +Fc5CzfQAhw57y6LmnPVoKAE/TFHvvSFNxjwSaBCGQ46FfnZMjs48a2xwHaqwAw== +-----END CERTIFICATE----- +` + pems := splitCert([]byte(PEM)) + for i := 0; i < len(pems); i += 1 { + t.Logf("\n%s\n", string(pems[i])) + } +}