Skip to content

Commit

Permalink
Merge pull request #193 from Ethernal-Tech/feat/tls-command
Browse files Browse the repository at this point in the history
Create TLS certificate with command / load TLS certificate from configuration
  • Loading branch information
oliverbundalo authored Apr 12, 2024
2 parents 6dda350 + 2cb012f commit 5d6017f
Show file tree
Hide file tree
Showing 17 changed files with 252 additions and 34 deletions.
10 changes: 5 additions & 5 deletions .github/workflows/e2e-polybft-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,19 @@ jobs:
go-version: 1.21.x
check-latest: true
- name: Generate OpenSSL certificate
run: openssl req -x509 -out jsontls.crt -keyout jsontls.key -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -extensions EXT -config <(printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
run: openssl req -x509 -out localhost.crt -keyout localhost.key -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' -extensions EXT -config <(printf "[dn]\nCN=localhost\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:localhost\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth")
- name: Copy certificate key
run: sudo cp jsontls.key /etc/ssl/private/jsontls.key
run: sudo cp localhost.key /etc/ssl/private/localhost.key
- name: Copy certificate itself
run: sudo cp jsontls.crt /usr/local/share/ca-certificates/jsontls.crt
run: sudo cp localhost.crt /usr/local/share/ca-certificates/localhost.crt
- name: Add certificate to trusted list
run: sudo update-ca-certificates
- name: Update certificate key folder permissions
run: sudo chmod -R 755 /etc/ssl/private
- name: Update certificate key file permissions
run: sudo chmod 644 /etc/ssl/private/jsontls.key
run: sudo chmod 644 /etc/ssl/private/localhost.key
- name: Check certificate key permissions
run: ls -l /etc/ssl/private/jsontls.key
run: ls -l /etc/ssl/private/localhost.key
- name: Run tests
run: make test-e2e-polybft
- name: Run tests failed
Expand Down
33 changes: 31 additions & 2 deletions command/secrets/init/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
privateKeyFlag = "private"
insecureLocalStoreFlag = "insecure"
networkFlag = "network"
jsonTLSCertFlag = "json-tls-cert"
numFlag = "num"
outputFlag = "output"

Expand All @@ -29,8 +30,9 @@ type initParams struct {
accountDir string
accountConfig string

generatesAccount bool
generatesNetwork bool
generatesAccount bool
generatesNetwork bool
generatesJSONTLSCert bool

printPrivateKey bool

Expand Down Expand Up @@ -96,6 +98,13 @@ func (ip *initParams) setFlags(cmd *cobra.Command) {
"the flag indicating whether new Network key is created",
)

cmd.Flags().BoolVar(
&ip.generatesJSONTLSCert,
jsonTLSCertFlag,
true,
"the flag indicating whether a new self signed TLS certificate is created for JSON RPC",
)

cmd.Flags().BoolVar(
&ip.printPrivateKey,
privateKeyFlag,
Expand Down Expand Up @@ -171,6 +180,12 @@ func (ip *initParams) initKeys(secretsManager secrets.SecretsManager) ([]string,
}
}

if ip.generatesJSONTLSCert {
if err := ip.generateJSONTLSCert(secretsManager, &generated); err != nil {
return generated, err
}
}

return generated, nil
}

Expand Down Expand Up @@ -207,6 +222,20 @@ func (ip *initParams) generateAccount(secretsManager secrets.SecretsManager, gen
return nil
}

func (ip *initParams) generateJSONTLSCert(secretsManager secrets.SecretsManager, generated *[]string) error {
if secretsManager.HasSecret(secrets.JSONTLSCert) && secretsManager.HasSecret(secrets.JSONTLSKey) {
return nil
}

if err := helper.InitJSONTLSCert(secretsManager); err != nil {
return fmt.Errorf("error initializing json tls certificate: %w", err)
}

*generated = append(*generated, secrets.JSONTLSCert, secrets.JSONTLSKey)

return nil
}

// getResult gets keys from secret manager and return result to display
func (ip *initParams) getResult(
secretsManager secrets.SecretsManager,
Expand Down
26 changes: 21 additions & 5 deletions command/secrets/init/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ func Test_initKeys(t *testing.T) {
require.NoError(t, err)

ip := &initParams{
generatesAccount: false,
generatesNetwork: false,
generatesAccount: false,
generatesNetwork: false,
generatesJSONTLSCert: false,
}

_, err = ip.initKeys(sm)
Expand All @@ -38,6 +39,8 @@ func Test_initKeys(t *testing.T) {
assert.False(t, fileExists(path.Join(dir, "consensus/validator.key")))
assert.False(t, fileExists(path.Join(dir, "consensus/validator-bls.key")))
assert.False(t, fileExists(path.Join(dir, "libp2p/libp2p.key")))
assert.False(t, fileExists(path.Join(dir, "jsontls/jsontls.pem")))
assert.False(t, fileExists(path.Join(dir, "jsontls/jsontls.key")))

ip.generatesAccount = true
res, err := ip.initKeys(sm)
Expand All @@ -47,13 +50,25 @@ func Test_initKeys(t *testing.T) {
assert.True(t, fileExists(path.Join(dir, "consensus/validator.key")))
assert.True(t, fileExists(path.Join(dir, "consensus/validator-bls.key")))
assert.False(t, fileExists(path.Join(dir, "libp2p/libp2p.key")))
assert.False(t, fileExists(path.Join(dir, "jsontls/jsontls.pem")))
assert.False(t, fileExists(path.Join(dir, "jsontls/jsontls.key")))

ip.generatesNetwork = true
res, err = ip.initKeys(sm)
require.NoError(t, err)
assert.Len(t, res, 1)

assert.True(t, fileExists(path.Join(dir, "libp2p/libp2p.key")))
assert.False(t, fileExists(path.Join(dir, "jsontls/jsontls.pem")))
assert.False(t, fileExists(path.Join(dir, "jsontls/jsontls.key")))

ip.generatesJSONTLSCert = true
res, err = ip.initKeys(sm)
require.NoError(t, err)
assert.Len(t, res, 2)

assert.True(t, fileExists(path.Join(dir, "jsontls/jsontls.pem")))
assert.True(t, fileExists(path.Join(dir, "jsontls/jsontls.key")))
}

func fileExists(filename string) bool {
Expand All @@ -78,9 +93,10 @@ func Test_getResult(t *testing.T) {
require.NoError(t, err)

ip := &initParams{
generatesAccount: true,
generatesNetwork: true,
printPrivateKey: true,
generatesAccount: true,
generatesNetwork: true,
generatesJSONTLSCert: true,
printPrivateKey: true,
}

_, err = ip.initKeys(sm)
Expand Down
7 changes: 6 additions & 1 deletion command/secrets/output/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,14 @@ func (op *outputParams) parseConfig() error {
func (op *outputParams) initLocalSecretsManager() error {
validatorPathPrefix := filepath.Join(op.dataDir, secrets.ConsensusFolderLocal)
networkPathPrefix := filepath.Join(op.dataDir, secrets.NetworkFolderLocal)
jsonTLSPathPrefix := filepath.Join(op.dataDir, secrets.JSONTLSFolderLocal)
dataDirAbs, _ := filepath.Abs(op.dataDir)

if !common.DirectoryExists(op.dataDir) {
return fmt.Errorf("the data directory provided does not exist: %s", dataDirAbs)
}

errs := make([]string, 0, 2)
errs := make([]string, 0, 3)
if !common.DirectoryExists(validatorPathPrefix) {
errs = append(errs, fmt.Sprintf("no validator keys found in the data directory provided: %s", dataDirAbs))
}
Expand All @@ -130,6 +131,10 @@ func (op *outputParams) initLocalSecretsManager() error {
errs = append(errs, fmt.Sprintf("no network key found in the data directory provided: %s", dataDirAbs))
}

if !common.DirectoryExists(jsonTLSPathPrefix) {
errs = append(errs, fmt.Sprintf("no json tls certificate found in the data directory provided: %s", dataDirAbs))
}

if len(errs) > 0 {
return fmt.Errorf(strings.Join(errs, "\n"))
}
Expand Down
4 changes: 4 additions & 0 deletions command/server/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type Config struct {
JSONLogFormat bool `json:"json_log_format" yaml:"json_log_format"`
CorsAllowedOrigins []string `json:"cors_allowed_origins" yaml:"cors_allowed_origins"`
UseTLS bool `json:"use_tls" yaml:"use_tls"`
TLSCertFile string `json:"tls_cert_file" yaml:"tls_cert_file"`
TLSKeyFile string `json:"tls_key_file" yaml:"tls_key_file"`

Relayer bool `json:"relayer" yaml:"relayer"`

Expand Down Expand Up @@ -146,6 +148,8 @@ func DefaultConfig() *Config {
},
LogFilePath: "",
UseTLS: false,
TLSCertFile: "",
TLSKeyFile: "",
JSONRPCBatchRequestLimit: DefaultJSONRPCBatchRequestLimit,
JSONRPCBlockRangeLimit: DefaultJSONRPCBlockRangeLimit,
Relayer: false,
Expand Down
4 changes: 4 additions & 0 deletions command/server/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const (
corsOriginFlag = "access-control-allow-origins"
logFileLocationFlag = "log-to"
useTLSFlag = "use-tls"
tlsCertFileLocationFlag = "tls-cert-file"
tlsKeyFileLocationFlag = "tls-key-file"

relayerFlag = "relayer"

Expand Down Expand Up @@ -185,6 +187,8 @@ func (p *serverParams) generateConfig() *server.Config {
JSONLogFormat: p.rawConfig.JSONLogFormat,
LogFilePath: p.logFileLocation,
UseTLS: p.rawConfig.UseTLS,
TLSCertFile: p.rawConfig.TLSCertFile,
TLSKeyFile: p.rawConfig.TLSKeyFile,

Relayer: p.relayer,
MetricsInterval: p.rawConfig.MetricsInterval,
Expand Down
14 changes: 14 additions & 0 deletions command/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,20 @@ func setFlags(cmd *cobra.Command) {
"start json rpc endpoint with tls enabled",
)

cmd.Flags().StringVar(
&params.rawConfig.TLSCertFile,
tlsCertFileLocationFlag,
defaultConfig.TLSCertFile,
"path to TLS cert file, if no file is provided then cert file is loaded from secrets manager",
)

cmd.Flags().StringVar(
&params.rawConfig.TLSKeyFile,
tlsKeyFileLocationFlag,
defaultConfig.TLSKeyFile,
"path to TLS key file, if no file is provided then key file is loaded from secrets manager",
)

cmd.Flags().BoolVar(
&params.rawConfig.Relayer,
relayerFlag,
Expand Down
27 changes: 27 additions & 0 deletions e2e-polybft/e2e/jsonrpc_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package e2e

import (
"crypto/tls"
"math/big"
"net/http"
"testing"
"time"

Expand Down Expand Up @@ -32,6 +34,7 @@ func TestE2E_JsonRPC(t *testing.T) {
framework.WithPremine(preminedAcct.Address()),
framework.WithBurnContract(&polybft.BurnContractInfo{BlockNumber: 0, Address: types.ZeroAddress}),
framework.WithHTTPS(),
framework.WithTLSCertificate("/etc/ssl/certs/localhost.pem", "/etc/ssl/private/localhost.key"),
)
defer cluster.Stop()

Expand Down Expand Up @@ -310,3 +313,27 @@ func TestE2E_JsonRPC(t *testing.T) {
require.Equal(t, txReceipt.BlockHash, ethgo.Hash(header.Hash))
})
}

func TestE2E_JsonRPCSelfSignedTLS(t *testing.T) {
cluster := framework.NewTestCluster(t, 4,
framework.WithHTTPS(),
)
defer cluster.Stop()

// Wait for endpoint to start, can't use cluster.WaitForReady because server certificate is not trusted by client
time.Sleep(1 * time.Second)

addr := cluster.Servers[0].JSONRPCAddr()
tr := &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
}
client := &http.Client{Transport: tr}
_, err := client.Get(addr)
require.NoError(t, err)

// This will fail with certificate signed by unknown authority error
client = &http.Client{}
_, err = client.Get(addr)
require.Error(t, err)
require.ErrorContains(t, err, "x509: certificate signed by unknown authority")
}
13 changes: 12 additions & 1 deletion e2e-polybft/framework/test-cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,9 @@ type TestClusterConfig struct {

logsDirOnce sync.Once

UseTLS bool
UseTLS bool
TLSCertFile string
TLSKeyFile string
}

func (c *TestClusterConfig) Dir(name string) string {
Expand Down Expand Up @@ -469,6 +471,13 @@ func WithHTTPS() ClusterOption {
}
}

func WithTLSCertificate(certFile string, keyFile string) ClusterOption {
return func(h *TestClusterConfig) {
h.TLSCertFile = certFile
h.TLSKeyFile = keyFile
}
}

func isTrueEnv(e string) bool {
return strings.ToLower(os.Getenv(e)) == "true"
}
Expand Down Expand Up @@ -812,6 +821,8 @@ func (c *TestCluster) InitTestServer(t *testing.T,
config.NumBlockConfirmations = c.Config.NumBlockConfirmations
config.BridgeJSONRPC = bridgeJSONRPC
config.UseTLS = c.Config.UseTLS
config.TLSCertFile = c.Config.TLSCertFile
config.TLSKeyFile = c.Config.TLSKeyFile
})

// watch the server for stop signals. It is important to fix the specific
Expand Down
6 changes: 6 additions & 0 deletions e2e-polybft/framework/test-server.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ type TestServerConfig struct {
NumBlockConfirmations uint64
BridgeJSONRPC string
UseTLS bool
TLSCertFile string
TLSKeyFile string
}

type TestServerConfigCallback func(*TestServerConfig)
Expand Down Expand Up @@ -170,6 +172,10 @@ func (t *TestServer) Start() {
"--jsonrpc", fmt.Sprintf(":%d", config.JSONRPCPort),
// minimal number of child blocks required for the parent block to be considered final
"--num-block-confirmations", strconv.FormatUint(config.NumBlockConfirmations, 10),
// TLS certificate file
"--tls-cert-file", config.TLSCertFile,
// TLS key file
"--tls-key-file", config.TLSKeyFile,
}

if len(config.LogLevel) > 0 {
Expand Down
41 changes: 28 additions & 13 deletions jsonrpc/jsonrpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type Config struct {
ConcurrentRequestsDebug uint64
WebSocketReadLimit uint64
UseTLS bool
TLSCertFile string
TLSKeyFile string
SecretsManager secrets.SecretsManager
}

Expand Down Expand Up @@ -116,23 +118,36 @@ func (j *JSONRPC) setupHTTP() error {
if j.config.UseTLS {
j.logger.Info("configuring http server with tls...")

cert, err := loadTLSCertificate(j.config.SecretsManager)
if err != nil {
j.logger.Error("loading tls certificate", "err", err)
if j.config.TLSCertFile != "" && j.config.TLSKeyFile != "" {
j.logger.Info("TLS", "cert file", j.config.TLSCertFile)
j.logger.Info("TLS", "key file", j.config.TLSKeyFile)

return err
}
go func() {
if err := srv.ServeTLS(lis, j.config.TLSCertFile, j.config.TLSKeyFile); err != nil {
j.logger.Error("closed https connection", "err", err)
}
}()
} else {
j.logger.Info("loading tls certificate from secrets manager...")

srv.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{*cert},
MinVersion: tls.VersionTLS12,
}
cert, err := loadTLSCertificate(j.config.SecretsManager)
if err != nil {
j.logger.Error("loading tls certificate", "err", err)

go func() {
if err := srv.ServeTLS(lis, "", ""); err != nil {
j.logger.Error("closed https connection", "err", err)
return err
}
}()

srv.TLSConfig = &tls.Config{
Certificates: []tls.Certificate{*cert},
MinVersion: tls.VersionTLS12,
}

go func() {
if err := srv.ServeTLS(lis, "", ""); err != nil {
j.logger.Error("closed https connection", "err", err)
}
}()
}
} else {
go func() {
if err := srv.Serve(lis); err != nil {
Expand Down
Loading

0 comments on commit 5d6017f

Please sign in to comment.