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

api: Add support for Private Network Access header preflight requests #6089

Merged
merged 10 commits into from
Sep 19, 2024
3 changes: 3 additions & 0 deletions config/localTemplate.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,9 @@ type Local struct {
// EndpointAddress configures the address the node listens to for REST API calls. Specify an IP and port or just port. For example, 127.0.0.1:0 will listen on a random port on the localhost (preferring 8080).
EndpointAddress string `version[0]:"127.0.0.1:0"`

// Respond to Private Network Access preflight requests sent to the node. Useful when a public website is trying to access a node that's hosted on a local network.
EnablePrivateNetworkAccessHeader bool `version[34]:"false"`

// RestReadTimeoutSeconds is passed to the API servers rest http.Server implementation.
RestReadTimeoutSeconds int `version[4]:"15"`

Expand Down
1 change: 1 addition & 0 deletions config/local_defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ var defaultLocal = Local{
EnableP2P: false,
EnableP2PHybridMode: false,
EnablePingHandler: true,
EnablePrivateNetworkAccessHeader: false,
EnableProcessBlockStats: false,
EnableProfiler: false,
EnableRequestLogger: false,
Expand Down
13 changes: 13 additions & 0 deletions daemon/algod/api/server/lib/middlewares/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,16 @@
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodOptions},
})
}

// MakePNA constructs the Private Network Access middleware function
func MakePNA() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
req := ctx.Request()
if req.Method == http.MethodOptions && req.Header.Get("Access-Control-Request-Private-Network") == "true" {
ctx.Response().Header().Set("Access-Control-Allow-Private-Network", "true")

Check warning on line 41 in daemon/algod/api/server/lib/middlewares/cors.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/lib/middlewares/cors.go#L36-L41

Added lines #L36 - L41 were not covered by tests
}
return next(ctx)

Check warning on line 43 in daemon/algod/api/server/lib/middlewares/cors.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/lib/middlewares/cors.go#L43

Added line #L43 was not covered by tests
}
}
}
6 changes: 6 additions & 0 deletions daemon/algod/api/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@
middleware.RemoveTrailingSlash())
e.Use(
middlewares.MakeLogger(logger),
)

Check warning on line 110 in daemon/algod/api/server/router.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/router.go#L110

Added line #L110 was not covered by tests
// Optional middleware for Private Network Access Header (PNA). Must come before CORS middleware.
if node.Config().EnablePrivateNetworkAccessHeader {
e.Use(middlewares.MakePNA())

Check warning on line 113 in daemon/algod/api/server/router.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/router.go#L112-L113

Added lines #L112 - L113 were not covered by tests
}
e.Use(

Check warning on line 115 in daemon/algod/api/server/router.go

View check run for this annotation

Codecov / codecov/patch

daemon/algod/api/server/router.go#L115

Added line #L115 was not covered by tests
middlewares.MakeCORS(TokenHeader),
)

Expand Down
5 changes: 4 additions & 1 deletion daemon/kmd/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,10 +137,13 @@ func SwaggerHandler(w http.ResponseWriter, r *http.Request) {

// Handler returns the root mux router for the kmd API. It sets up handlers on
// subrouters specific to each API version.
func Handler(sm *session.Manager, log logging.Logger, allowedOrigins []string, apiToken string, reqCB func()) *mux.Router {
func Handler(sm *session.Manager, log logging.Logger, allowedOrigins []string, apiToken string, pnaHeader bool, reqCB func()) *mux.Router {
rootRouter := mux.NewRouter()

// Send the appropriate CORS headers
if pnaHeader {
rootRouter.Use(AllowPNA())
gmalouf marked this conversation as resolved.
Show resolved Hide resolved
}
rootRouter.Use(corsMiddleware(allowedOrigins))

// Handle OPTIONS requests
Expand Down
13 changes: 13 additions & 0 deletions daemon/kmd/api/cors.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,16 @@ func corsMiddleware(allowedOrigins []string) func(http.Handler) http.Handler {
})
}
}

// AllowPNA constructs the Private Network Access middleware function
func AllowPNA() func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodOptions && r.Header.Get("Access-Control-Request-Private-Network") == "true" {
w.Header().Set("Access-Control-Allow-Private-Network", "true")
}

next.ServeHTTP(w, r)
})
}
}
1 change: 1 addition & 0 deletions daemon/kmd/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type KMDConfig struct {
SessionLifetimeSecs uint64 `json:"session_lifetime_secs"`
Address string `json:"address"`
AllowedOrigins []string `json:"allowed_origins"`
AllowHeaderPNA bool `json:"allow_header_pna"`
}

// DriverConfig contains config info specific to each wallet driver
Expand Down
1 change: 1 addition & 0 deletions daemon/kmd/kmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func Start(startConfig StartConfig) (died chan error, sock string, err error) {
DataDir: startConfig.DataDir,
Address: kmdCfg.Address,
AllowedOrigins: kmdCfg.AllowedOrigins,
AllowHeaderPNA: kmdCfg.AllowHeaderPNA,
SessionManager: session.MakeManager(kmdCfg),
Log: startConfig.Log,
Timeout: startConfig.Timeout,
Expand Down
3 changes: 2 additions & 1 deletion daemon/kmd/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ type WalletServerConfig struct {
DataDir string
Address string
AllowedOrigins []string
AllowHeaderPNA bool
SessionManager *session.Manager
Log logging.Logger
Timeout *time.Duration
Expand Down Expand Up @@ -211,7 +212,7 @@ func (ws *WalletServer) start(kill chan os.Signal) (died chan error, sock string
// Initialize HTTP server
watchdogCB := ws.makeWatchdogCallback(kill)
srv := http.Server{
Handler: api.Handler(ws.SessionManager, ws.Log, ws.AllowedOrigins, ws.APIToken, watchdogCB),
Handler: api.Handler(ws.SessionManager, ws.Log, ws.AllowedOrigins, ws.APIToken, ws.AllowHeaderPNA, watchdogCB),
}

// Read the kill channel and shut down the server gracefully
Expand Down
1 change: 1 addition & 0 deletions installer/config.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"EnableP2P": false,
"EnableP2PHybridMode": false,
"EnablePingHandler": true,
"EnablePrivateNetworkAccessHeader": false,
"EnableProcessBlockStats": false,
"EnableProfiler": false,
"EnableRequestLogger": false,
Expand Down
6 changes: 5 additions & 1 deletion test/e2e-go/cli/goal/expect/corsTest.exp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if { [catch {
set TEST_ROOT_DIR $TEST_ALGO_DIR/root
set TEST_PRIMARY_NODE_DIR $TEST_ROOT_DIR/Primary/
set NETWORK_NAME test_net_expect_$TIME_STAMP
set NETWORK_TEMPLATE "$TEST_DATA_DIR/nettemplates/TwoNodes50Each.json"
set NETWORK_TEMPLATE "$TEST_DATA_DIR/nettemplates/TwoNodes50EachPNA.json"

# Create network
::AlgorandGoal::CreateNetwork $NETWORK_NAME $NETWORK_TEMPLATE $TEST_ALGO_DIR $TEST_ROOT_DIR
Expand All @@ -31,6 +31,10 @@ if { [catch {
set ALGOD_NET_ADDRESS [::AlgorandGoal::GetAlgodNetworkAddress $TEST_PRIMARY_NODE_DIR]
::AlgorandGoal::CheckNetworkAddressForCors $ALGOD_NET_ADDRESS

# Hit algod with a private network access preflight request and look for 200 OK
gmalouf marked this conversation as resolved.
Show resolved Hide resolved
set ALGOD_NET_ADDRESS [::AlgorandGoal::GetAlgodNetworkAddress $TEST_PRIMARY_NODE_DIR]
::AlgorandGoal::CheckNetworkAddressForPNA $ALGOD_NET_ADDRESS

# Start kmd, then do the same CORS check as algod
exec goal kmd start -t 180 -d $TEST_PRIMARY_NODE_DIR
set KMD_NET_ADDRESS [::AlgorandGoal::GetKMDNetworkAddress $TEST_PRIMARY_NODE_DIR]
Expand Down
17 changes: 17 additions & 0 deletions test/e2e-go/cli/goal/expect/goalExpectCommon.exp
Original file line number Diff line number Diff line change
Expand Up @@ -817,6 +817,23 @@ proc ::AlgorandGoal::CheckNetworkAddressForCors { NET_ADDRESS } {
}
}

# Use curl to check if a network address supports private network access
proc ::AlgorandGoal::CheckNetworkAddressForPNA { NET_ADDRESS } {
if { [ catch {
algorandskiy marked this conversation as resolved.
Show resolved Hide resolved
spawn curl -X OPTIONS -H "Access-Control-Request-Private-Network: true" --head $NET_ADDRESS
expect {
timeout { close; ::AlgorandGoal::Abort "Timeout failure in CheckNetworkAddressForPNA" }
"Access-Control-Allow-Private-Network" { puts "success" ; close }
eof {
return -code error "EOF without Access-Control-Allow-Private-Network"
}
close
}
} EXCEPTION ] } {
::AlgorandGoal::Abort "ERROR in CheckNetworkAddressForPNA: $EXCEPTION"
}
}

# Show the Ledger Supply
proc ::AlgorandGoal::GetLedgerSupply { TEST_PRIMARY_NODE_DIR } {
if { [ catch {
Expand Down
1 change: 1 addition & 0 deletions test/testdata/configs/config-v34.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
"EnableP2P": false,
"EnableP2PHybridMode": false,
"EnablePingHandler": true,
"EnablePrivateNetworkAccessHeader": false,
"EnableProcessBlockStats": false,
"EnableProfiler": false,
"EnableRequestLogger": false,
Expand Down
37 changes: 37 additions & 0 deletions test/testdata/nettemplates/TwoNodes50EachPNA.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"Genesis": {
"NetworkName": "tbd",
"LastPartKeyRound": 3000,
"Wallets": [
{
"Name": "Wallet1",
"Stake": 50,
"Online": true
},
{
"Name": "Wallet2",
"Stake": 50,
"Online": true
}
]
},
"Nodes": [
{
"Name": "Primary",
"IsRelay": true,
"ConfigJSONOverride": "{\"EnablePrivateNetworkAccessHeader\":true}",
"Wallets": [
{ "Name": "Wallet1",
"ParticipationOnly": false }
]
},
{
"Name": "Node",
"ConfigJSONOverride": "{\"EnablePrivateNetworkAccessHeader\":true}",
"Wallets": [
{ "Name": "Wallet2",
"ParticipationOnly": false }
]
}
]
}
Loading