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

Create TLS certificate with command / load TLS certificate from configuration #193

Merged
merged 3 commits into from
Apr 12, 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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading