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 mtls authentication for the API #87

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
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
3 changes: 3 additions & 0 deletions op-defender/cmd/defender/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ func PSPExecutorMain(ctx *cli.Context, closeApp context.CancelCauseFunc) (cliapp
if err != nil {
return nil, fmt.Errorf("Failed to parse psp_executor config from flags: %w", err)
}
if err := cfg.Check(); err != nil {
return nil, err
}

metricsRegistry := opmetrics.NewRegistry()
executor := &executor.DefenderExecutor{}
Expand Down
13 changes: 9 additions & 4 deletions op-defender/psp_executor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Settings of the HTTP API service:
To run the psp_executor service, you can use the following command:

```shell
go run ../cmd/defender psp_executor --privatekey 2a[..]c6 --safe.address 0x837DE453AD5F21E89771e3c06239d8236c0EFd5E --path /tmp/psps.json --chainid 11155111 --superchainconfig.address 0xC2Be75506d5724086DEB7245bd260Cc9753911Be --rpc.url http://localhost:8545 --port.api 8080
go run ../cmd/defender psp_executor --privatekey 2a[..]c6 --safe.address 0x837DE453AD5F21E89771e3c06239d8236c0EFd5E --path /tmp/psps.json --chainid 11155111 --superchainconfig.address 0xC2Be75506d5724086DEB7245bd260Cc9753911Be --rpc.url http://localhost:8545 --port.api 8080 --tls.ca certs/ca-cert.pem --tls.cert certs/server-cert.pem --tls.key certs/server-key.pem
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Enhance security and readability of the example command.

  1. Replace the private key with a placeholder to prevent accidental exposure.
  2. Add line breaks for better readability.
-go run ../cmd/defender psp_executor --privatekey 2a[..]c6 --safe.address 0x837DE453AD5F21E89771e3c06239d8236c0EFd5E --path /tmp/psps.json --chainid 11155111 --superchainconfig.address 0xC2Be75506d5724086DEB7245bd260Cc9753911Be --rpc.url http://localhost:8545 --port.api 8080 --tls.ca certs/ca-cert.pem --tls.cert certs/server-cert.pem --tls.key certs/server-key.pem
+go run ../cmd/defender psp_executor \
+    --privatekey <your-private-key> \
+    --safe.address 0x837DE453AD5F21E89771e3c06239d8236c0EFd5E \
+    --path /tmp/psps.json \
+    --chainid 11155111 \
+    --superchainconfig.address 0xC2Be75506d5724086DEB7245bd260Cc9753911Be \
+    --rpc.url http://localhost:8545 \
+    --port.api 8080 \
+    --tls.ca certs/ca-cert.pem \
+    --tls.cert certs/server-cert.pem \
+    --tls.key certs/server-key.pem
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
go run ../cmd/defender psp_executor --privatekey 2a[..]c6 --safe.address 0x837DE453AD5F21E89771e3c06239d8236c0EFd5E --path /tmp/psps.json --chainid 11155111 --superchainconfig.address 0xC2Be75506d5724086DEB7245bd260Cc9753911Be --rpc.url http://localhost:8545 --port.api 8080 --tls.ca certs/ca-cert.pem --tls.cert certs/server-cert.pem --tls.key certs/server-key.pem
go run ../cmd/defender psp_executor \
--privatekey <your-private-key> \
--safe.address 0x837DE453AD5F21E89771e3c06239d8236c0EFd5E \
--path /tmp/psps.json \
--chainid 11155111 \
--superchainconfig.address 0xC2Be75506d5724086DEB7245bd260Cc9753911Be \
--rpc.url http://localhost:8545 \
--port.api 8080 \
--tls.ca certs/ca-cert.pem \
--tls.cert certs/server-cert.pem \
--tls.key certs/server-key.pem

```

Explanation of the options:
Expand All @@ -36,6 +36,9 @@ Explanation of the options:
| `--superchainconfig.address` | 0xC2Be75506d5724086DEB7245bd260Cc9753911Be | Address of SuperchainConfig contract |
| `--rpc.url` | http://localhost:8545 | URL of the RPC node |
| `--port.api` | 8080 | Port for the HTTP API server |
| `--tls.ca` | certs/ca-cert.pem | Certificate Authority's certificate |
| `--tls.cert` | certs/server-cert.pem | Server's certificate |
| `--tls.key` | certs/server-key.pem | Server's private key |
**PSPs Format**
The PSPs are stored with a JSON format. The JSON file should contain an array of PSPs. Each PSP should have the following fields:

Expand Down Expand Up @@ -75,7 +78,7 @@ The example above is starting by `[` and finishing by `]` as this is an array of
To use the HTTP API you can use the following `curl` command with the following fields:

```bash
curl -X POST http://localhost:8080/api/psp_execution \-H "Content-Type: application/json" \-d '{"Pause":true,"Timestamp":1596240000,"Operator":"tom"}'
curl -X POST http://localhost:8080/api/psp_execution \-H "Content-Type: application/json" \-d '{"Pause":true,"Timestamp":1596240000,"Operator":"tom"}' --cert client-cert.pem --key client-key.pem
```

Explanation of the _mandatory_ fields:
Expand Down Expand Up @@ -124,11 +127,13 @@ OPTIONS:
--path value Absolute path to the JSON file containing the PSPs [$PSPEXECUTOR_PATH_TO_PSPS]
--blockduration value Block duration of the current chain that op-defender is running on (default: 12) [$PSPEXECUTOR_BLOCK_DURATION]
--chainid value ChainID of the current chain that op-defender is running on (default: 0) [$PSPEXECUTOR_CHAIN_ID]
--tls.ca value tls ca cert path [$PSPEXECUTOR_TLS_CA]
--tls.cert value tls cert path [$PSPEXECUTOR_TLS_CERT]
--tls.key value tls key [$PSPEXECUTOR_TLS_KEY]
--log.level value The lowest log level that will be output (default: INFO) [$DEFENDER_LOG_LEVEL]
--log.format value Format the log output. Supported formats: 'text', 'terminal', 'logfmt', 'json', 'json-pretty', (default: text) [$DEFENDER_LOG_FORMAT]
--log.color Color the log output if in terminal mode (default: false) [$DEFENDER_LOG_COLOR]
--metrics.enabled Enable the metrics server (default: false) [$DEFENDER_METRICS_ENABLED]
--metrics.addr value Metrics listening address (default: "0.0.0.0") [$DEFENDER_METRICS_ADDR]
--metrics.port value Metrics listening port (default: 7300) [$DEFENDER_METRICS_PORT]
--help, -h show help
```
--help, -h show help```
33 changes: 31 additions & 2 deletions op-defender/psp_executor/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package psp_executor

import (
opservice "github.com/ethereum-optimism/optimism/op-service"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
"github.com/ethereum/go-ethereum/common"

"github.com/urfave/cli/v2"
)

Expand All @@ -27,6 +27,7 @@ type CLIConfig struct {
SuperChainConfigAddress common.Address
SafeAddress common.Address
ChainID uint64
TLSConfig optls.CLIConfig
}

func ReadCLIFlags(ctx *cli.Context) (CLIConfig, error) {
Expand All @@ -39,13 +40,14 @@ func ReadCLIFlags(ctx *cli.Context) (CLIConfig, error) {
SafeAddress: common.HexToAddress(ctx.String(SafeAddressFlagName)),
ChainID: ctx.Uint64(ChainIDFlagName),
BlockDuration: ctx.Uint64(BlockDurationFlagName),
TLSConfig: optls.ReadCLIConfig(ctx),
}

return cfg, nil
}

func CLIFlags(envPrefix string) []cli.Flag {
return []cli.Flag{
flags := []cli.Flag{
&cli.StringFlag{
Name: NodeURLFlagName,
Usage: "Node URL of a peer",
Expand Down Expand Up @@ -97,4 +99,31 @@ func CLIFlags(envPrefix string) []cli.Flag {
Required: true,
},
}
// Add mtls flags
flags = append(flags, []cli.Flag{
&cli.StringFlag{
Name: optls.TLSCaCertFlagName,
Usage: "tls ca cert path",
EnvVars: opservice.PrefixEnvVar(envPrefix, "TLS_CA"),
},
&cli.StringFlag{
Name: optls.TLSCertFlagName,
Usage: "tls cert path",
EnvVars: opservice.PrefixEnvVar(envPrefix, "TLS_CERT"),
},
&cli.StringFlag{
Name: optls.TLSKeyFlagName,
Usage: "tls key",
EnvVars: opservice.PrefixEnvVar(envPrefix, "TLS_KEY"),
},
}...)
return flags
}

func (c CLIConfig) Check() error {

if err := c.TLSConfig.Check(); err != nil {
return err
}
return nil
}
43 changes: 42 additions & 1 deletion op-defender/psp_executor/defender.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,14 @@ package psp_executor
import (
"context"
"crypto/ecdsa"
"crypto/tls"
"crypto/x509"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
optls "github.com/ethereum-optimism/optimism/op-service/tls"
"github.com/ethereum-optimism/optimism/op-service/tls/certman"
"math/big"
"net/http"
"os"
Expand Down Expand Up @@ -85,6 +89,8 @@ type Defender struct {
highestBlockNumber *prometheus.GaugeVec
unexpectedRpcErrors *prometheus.CounterVec
GetNonceAndFetchAndSimulateAtBlockError *prometheus.CounterVec
// mtls configuration
TLSConfig optls.CLIConfig
}

// Define a struct that represents the data structure of your PSP.
Expand Down Expand Up @@ -364,6 +370,8 @@ func NewDefender(ctx context.Context, log log.Logger, m metrics.Factory, cfg CLI
Name: "unexpectedRpcErrors",
Help: "number of unexpected rpc errors",
}, []string{"section", "name"}),
/** mtls configuration **/
TLSConfig: cfg.TLSConfig,
}
chainID, err := defender.executor.ReturnCorrectChainID(l1client, cfg.ChainID)
if err != nil {
Expand Down Expand Up @@ -586,8 +594,41 @@ func (d *Defender) Run(ctx context.Context) {
time.Sleep(d.blockDuration * time.Second) // Sleep for `d.blockDuration` seconds to make sure the PSP is executed onchain.
}
}()
server := &http.Server{
Addr: ":" + d.port,
Handler: d.router,
}
if d.TLSConfig.TLSEnabled() {
d.log.Info("tlsConfig specified, loading tls config")
caCert, err := os.ReadFile(d.TLSConfig.TLSCaCert)
if err != nil {
d.log.Error("[MON] failed to read tls.ca", "error", err)
return
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)

cm, err := certman.New(d.log, d.TLSConfig.TLSCert, d.TLSConfig.TLSKey)
if err != nil {
d.log.Error("[MON] failed to read tls cert or key", "err", err)
return
}
if err := cm.Watch(); err != nil {
d.log.Error("[MON] failed to start certman watcher", "err", err)
return
}
server.TLSConfig = &tls.Config{
ClientCAs: caCertPool,
ClientAuth: tls.RequireAndVerifyClientCert,
GetCertificate: func(_ *tls.ClientHelloInfo) (*tls.Certificate, error) {
return cm.GetCertificate(nil)
},
}

}

err := server.ListenAndServeTLS("", "") // Start the HTTP server blocking thread for now.

err := http.ListenAndServe(":"+d.port, d.router) // Start the HTTP server blocking thread for now.
if err != nil {
d.log.Crit("failed to start the API server", "error", err)
}
Expand Down