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

feat: add tls config, tls certificate from peer side can be verified. #393

Merged
merged 1 commit into from Nov 15, 2022
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
12 changes: 2 additions & 10 deletions core/server.go
Expand Up @@ -17,7 +17,6 @@ import (
// authentication implements, Currently, only token authentication is implemented
_ "github.com/yomorun/yomo/pkg/auth"
"github.com/yomorun/yomo/pkg/logger"
pkgtls "github.com/yomorun/yomo/pkg/tls"
)

const (
Expand Down Expand Up @@ -110,7 +109,7 @@ func (s *Server) Serve(ctx context.Context, conn net.PacketConn) error {
}
s.listener = listener
// defer listener.Close()
logger.Printf("%s✅ [%s][%d] Listening on: %s, MODE: %s, QUIC: %v, AUTH: %s", ServerLogPrefix, s.name, os.Getpid(), listener.Addr(), mode(), listener.Versions(), s.authNames())
logger.Printf("%s✅ [%s][%d] Listening on: %s, QUIC: %v, AUTH: %s", ServerLogPrefix, s.name, os.Getpid(), listener.Addr(), listener.Versions(), s.authNames())

for {
// create a new connection when new yomo-client connected
Expand Down Expand Up @@ -361,7 +360,7 @@ func (s *Server) handleHandshakeFrame(c *Context) error {
default:
// unknown client type
s.connector.Remove(connID)
err := fmt.Errorf("Illegal ClientType: %#x", f.ClientType)
err := fmt.Errorf("illegal ClientType: %#x", f.ClientType)
c.CloseWithError(yerr.ErrorCodeUnknownClient, err.Error())
return err
}
Expand Down Expand Up @@ -597,13 +596,6 @@ func (s *Server) authenticate(f *frame.HandshakeFrame) bool {
return true
}

func mode() string {
if pkgtls.IsDev() {
return "DEVELOPMENT"
}
return "PRODUCTION"
}

func authName(name string) string {
if name == "" {
return "empty"
Expand Down
2 changes: 1 addition & 1 deletion docs/optimizations.md
Expand Up @@ -18,7 +18,7 @@ You can read it in the [README.md](https://github.com/yomorun/yomo/blob/master/s

By default, we use the `development` development mode and do not perform mutual `TLS` authentication between the server and the client. In a production environment, it is **strongly recommended** you modify the following environment variables:

- `YOMO_ENV`, Set the value to `production`
- `YOMO_TLS_VERIFY_PEER`, Set the value to `true`
- `YOMO_TLS_CACERT_FILE`, CA certificate
- `YOMO_TLS_CERT_FILE`, Certificate
- `YOMO_TLS_KEY_FILE`, Private Key
Expand Down
2 changes: 1 addition & 1 deletion docs/zh-cn/optimizations.md
Expand Up @@ -18,7 +18,7 @@

默认情况下,我们使用 `development` 开发模式,不进行服务端与客户端的双向 `TLS`认证,在生产环境下,**强烈建议**您修改如下环境变量:

- `YOMO_ENV`,将该值设置为 `production`
- `YOMO_TLS_VERIFY_PEER`,将该值设置为 `true`
- `YOMO_TLS_CACERT_FILE`,CA 证书
- `YOMO_TLS_CERT_FILE`,证书
- `YOMO_TLS_KEY_FILE`,私钥
Expand Down
5 changes: 3 additions & 2 deletions example/3-multi-sfn/Taskfile.yml
Expand Up @@ -7,7 +7,7 @@ output: "prefixed"
env:
YOMO_LOG_LEVEL: error
# set the following environment values on production mode
# YOMO_ENV: production
# YOMO_TLS_VERIFY_PEER: true
# YOMO_TLS_CACERT_FILE: "../../test/tls/ca.crt"
# YOMO_TLS_CERT_FILE: "../../test/tls/client.crt"
# YOMO_TLS_KEY_FILE: "../../test/tls/client.key"
Expand All @@ -31,7 +31,8 @@ tasks:
YOMO_LOG_LEVEL: error
YOMO_ZIPPER_WORKFLOW: ./zipper/workflow.yaml
# set the following environment values on production mode
# YOMO_ENV: production
# YOMO_TLS_VERIFY_PEER: true
# YOMO_TLS_CACERT_FILE: "../../test/tls/ca.crt"
# YOMO_TLS_CERT_FILE: "../../test/tls/server.crt"
# YOMO_TLS_KEY_FILE: "../../test/tls/server.key"

Expand Down
120 changes: 48 additions & 72 deletions pkg/tls/tls.go
Expand Up @@ -10,85 +10,89 @@ import (
"crypto/x509/pkix"
"encoding/pem"
"errors"
"io/ioutil"
"math/big"
"net"
"os"
"strings"
"time"
)

var isDev bool

// CreateServerTLSConfig creates server tls config.
func CreateServerTLSConfig(host string) (*tls.Config, error) {
// development mode
if isDev {
tc, err := developmentTLSConfig(host)
if err != nil {
return nil, err
}
return tc, nil
}
// production mode
// ca pool
pool, err := getCACertPool()
if err != nil {
return nil, err
}

// server certificate
tlsCert, err := getCertAndKey()
if err != nil {
return nil, err
}

if tlsCert == nil {
tlsCert, err = generateCertificate(host)
if err != nil {
return nil, err
}
}

clientAuth := tls.NoClientCert
if verifyPeer() {
clientAuth = tls.RequireAndVerifyClientCert
}

return &tls.Config{
Certificates: []tls.Certificate{*tlsCert},
ClientCAs: pool,
ClientAuth: tls.RequireAndVerifyClientCert,
ClientAuth: clientAuth,
NextProtos: []string{"yomo"},
}, nil
}

// CreateClientTLSConfig creates client tls config.
func CreateClientTLSConfig() (*tls.Config, error) {
// development mode
if isDev {
return &tls.Config{
InsecureSkipVerify: true,
NextProtos: []string{"yomo"},
ClientSessionCache: tls.NewLRUClientSessionCache(64),
}, nil
}
// production mode
// ca pool
pool, err := getCACertPool()
if err != nil {
return nil, err
}

// client certificate
tlsCert, err := getCertAndKey()
if err != nil {
return nil, err
}

certificates := []tls.Certificate{}
if tlsCert != nil {
certificates = append(certificates, *tlsCert)
}

return &tls.Config{
InsecureSkipVerify: false,
Certificates: []tls.Certificate{*tlsCert},
InsecureSkipVerify: !verifyPeer(),
Certificates: certificates,
RootCAs: pool,
NextProtos: []string{"yomo"},
ClientSessionCache: tls.NewLRUClientSessionCache(0),
}, nil
}

func verifyPeer() bool {
return strings.ToLower(os.Getenv("YOMO_TLS_VERIFY_PEER")) == "true"
}

func getCACertPool() (*x509.CertPool, error) {
var err error
var caCert []byte

caCertPath := os.Getenv("YOMO_TLS_CACERT_FILE")
if len(caCertPath) == 0 {
return nil, errors.New("tls: must provide CA certificate on production mode, you can configure this via environment variables: `YOMO_TLS_CACERT_FILE`")
return nil, nil
}

caCert, err = ioutil.ReadFile(caCertPath)
caCert, err = os.ReadFile(caCertPath)
if err != nil {
return nil, err
}
Expand All @@ -112,16 +116,16 @@ func getCertAndKey() (*tls.Certificate, error) {
certPath := os.Getenv("YOMO_TLS_CERT_FILE")
keyPath := os.Getenv("YOMO_TLS_KEY_FILE")
if len(certPath) == 0 || len(keyPath) == 0 {
return nil, errors.New("tls: must provide certificate on production mode, you can configure this via environment variables: `YOMO_TLS_CERT_FILE` and `YOMO_TLS_KEY_FILE`")
return nil, nil
}

// certificate
cert, err = ioutil.ReadFile(certPath)
cert, err = os.ReadFile(certPath)
if err != nil {
return nil, err
}
// private key
key, err = ioutil.ReadFile(keyPath)
key, err = os.ReadFile(keyPath)
if err != nil {
return nil, err
}
Expand All @@ -138,29 +142,10 @@ func getCertAndKey() (*tls.Certificate, error) {
return &tlsCert, nil
}

// IsDev development mode
func IsDev() bool {
return isDev
}

// developmentTLSConfig Setup a bare-bones TLS config for the server
func developmentTLSConfig(host ...string) (*tls.Config, error) {
tlsCert, err := generateCertificate(host...)
if err != nil {
return nil, err
}

return &tls.Config{
Certificates: []tls.Certificate{tlsCert},
ClientSessionCache: tls.NewLRUClientSessionCache(1),
NextProtos: []string{"yomo"},
}, nil
}

func generateCertificate(host ...string) (tls.Certificate, error) {
func generateCertificate(host ...string) (*tls.Certificate, error) {
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
return tls.Certificate{}, err
return nil, err
}

notBefore := time.Now()
Expand All @@ -169,18 +154,16 @@ func generateCertificate(host ...string) (tls.Certificate, error) {
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
if err != nil {
return tls.Certificate{}, err
return nil, err
}

template := x509.Certificate{
SerialNumber: serialNumber,
Subject: pkix.Name{
Organization: []string{"YoMo"},
},
NotBefore: notBefore,
NotAfter: notAfter,

KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
SerialNumber: serialNumber,
Subject: pkix.Name{Organization: []string{"YoMo"}},
NotBefore: notBefore,
NotAfter: notAfter,
IsCA: true,
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
BasicConstraintsValid: true,
DNSNames: []string{"localhost"},
Expand All @@ -194,36 +177,29 @@ func generateCertificate(host ...string) (tls.Certificate, error) {
}
}

template.IsCA = true
template.KeyUsage |= x509.KeyUsageCertSign

derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
if err != nil {
return tls.Certificate{}, err
return nil, err
}

// create public key
certOut := bytes.NewBuffer(nil)
err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
if err != nil {
return tls.Certificate{}, err
return nil, err
}

// create private key
keyOut := bytes.NewBuffer(nil)
b, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return tls.Certificate{}, err
return nil, err
}
err = pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b})
if err != nil {
return tls.Certificate{}, err
return nil, err
}

return tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes())
}

func init() {
env := os.Getenv("YOMO_ENV")
isDev = len(env) == 0 || env != "production"
cert, err := tls.X509KeyPair(certOut.Bytes(), keyOut.Bytes())
return &cert, err
}
8 changes: 4 additions & 4 deletions scripts/README.md
Expand Up @@ -2,7 +2,7 @@

TLS is supported by YoMo. In order to run YoMo services with TLS encryption, it's not necessary to change any code or recompile the program; instead, the only thing you need to do is to add 4 environment variables when starting the service:

- `YOMO_ENV` ( = production )
- `YOMO_TLS_VERIFY_PEER`
- `YOMO_TLS_CACERT_FILE`
- `YOMO_TLS_CERT_FILE`
- `YOMO_TLS_KEY_FILE`
Expand Down Expand Up @@ -51,7 +51,7 @@ sudo echo '127.0.0.1 yomo-app.dev' | sudo tee -a /etc/hosts
## 3. Run YoMo Zipper (Server) with TLS encryption

```bash
YOMO_ENV=production \
YOMO_TLS_VERIFY_PEER=true \
YOMO_TLS_CACERT_FILE=tls/ca.crt \
YOMO_TLS_CERT_FILE=tls/server.crt \
YOMO_TLS_KEY_FILE=tls/server.key \
Expand All @@ -61,7 +61,7 @@ yomo serve -c ../example/0-basic/workflow.yaml
## 4. Run YoMo Stream Function (Client) with TLS encryption

```bash
YOMO_ENV=production \
YOMO_TLS_VERIFY_PEER=true \
YOMO_TLS_CACERT_FILE=tls/ca.crt \
YOMO_TLS_CERT_FILE=tls/client_sfn.crt \
YOMO_TLS_KEY_FILE=tls/client_sfn.key \
Expand All @@ -72,7 +72,7 @@ go run ../example/0-basic/sfn/main.go
## 5. Run YoMo Source (Client) with TLS encryption

```bash
YOMO_ENV=production \
YOMO_TLS_VERIFY_PEER=true \
YOMO_TLS_CACERT_FILE=tls/ca.crt \
YOMO_TLS_CERT_FILE=tls/client_source.crt \
YOMO_TLS_KEY_FILE=tls/client_source.key \
Expand Down