CCQ: Server should check api key first (#3443)
* CCQ: Server should check api key first * Add integration tests
This commit is contained in:
parent
bebcf281e6
commit
0cac01a739
|
@ -7,6 +7,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/certusone/wormhole/node/pkg/common"
|
"github.com/certusone/wormhole/node/pkg/common"
|
||||||
|
@ -18,6 +19,8 @@ import (
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const MAX_BODY_SIZE = 5 * 1024 * 1024
|
||||||
|
|
||||||
type queryRequest struct {
|
type queryRequest struct {
|
||||||
Bytes string `json:"bytes"`
|
Bytes string `json:"bytes"`
|
||||||
Signature string `json:"signature"`
|
Signature string `json:"signature"`
|
||||||
|
@ -43,25 +46,35 @@ func (s *httpServer) handleQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|
||||||
// Set CORS headers for the preflight request
|
// Set CORS headers for the preflight request
|
||||||
if r.Method == http.MethodOptions {
|
if r.Method == http.MethodOptions {
|
||||||
|
|
||||||
w.Header().Set("Access-Control-Allow-Methods", "PUT, POST")
|
w.Header().Set("Access-Control-Allow-Methods", "PUT, POST")
|
||||||
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Api-Key")
|
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Api-Key")
|
||||||
w.Header().Set("Access-Control-Max-Age", "3600")
|
w.Header().Set("Access-Control-Max-Age", "3600")
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var q queryRequest
|
|
||||||
err := json.NewDecoder(r.Body).Decode(&q)
|
// There should be one and only one API key in the header.
|
||||||
if err != nil {
|
apiKeys, exists := r.Header["X-Api-Key"]
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
if !exists || len(apiKeys) != 1 {
|
||||||
|
s.logger.Debug("received a request with the wrong number of api keys", zap.Stringer("url", r.URL), zap.Int("numApiKeys", len(apiKeys)))
|
||||||
|
http.Error(w, "api key is missing", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
apiKey := apiKeys[0]
|
||||||
|
|
||||||
|
// Make sure the user is authorized before we go any farther.
|
||||||
|
_, exists = s.permissions[strings.ToLower(apiKey)]
|
||||||
|
if !exists {
|
||||||
|
s.logger.Debug("invalid api key", zap.String("apiKey", apiKey))
|
||||||
|
http.Error(w, "invalid api key", http.StatusForbidden)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// There should be one and only one API key in the header.
|
var q queryRequest
|
||||||
apiKey, exists := r.Header["X-Api-Key"]
|
err := json.NewDecoder(http.MaxBytesReader(w, r.Body, MAX_BODY_SIZE)).Decode(&q)
|
||||||
if !exists || len(apiKey) != 1 {
|
if err != nil {
|
||||||
s.logger.Debug("received a request without an api key", zap.Stringer("url", r.URL), zap.Error(err))
|
s.logger.Debug("failed to decode body", zap.Error(err))
|
||||||
http.Error(w, "api key is missing", http.StatusUnauthorized)
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,7 +97,7 @@ func (s *httpServer) handleQuery(w http.ResponseWriter, r *http.Request) {
|
||||||
Signature: signature,
|
Signature: signature,
|
||||||
}
|
}
|
||||||
|
|
||||||
if status, err := validateRequest(s.logger, s.env, s.permissions, s.signerKey, apiKey[0], signedQueryRequest); err != nil {
|
if status, err := validateRequest(s.logger, s.env, s.permissions, s.signerKey, apiKey, signedQueryRequest); err != nil {
|
||||||
// Don't need to log here because the details were logged in the function.
|
// Don't need to log here because the details were logged in the function.
|
||||||
http.Error(w, err.Error(), status)
|
http.Error(w, err.Error(), status)
|
||||||
return
|
return
|
||||||
|
|
|
@ -277,4 +277,46 @@ describe("eth call", () => {
|
||||||
const response = await axios.get(HEALTH_URL);
|
const response = await axios.get(HEALTH_URL);
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
});
|
});
|
||||||
|
test("valid api key but payload too large should fail based on size", async () => {
|
||||||
|
const serialized = new Uint8Array(6000000); // Buffer should be larger than MAX_BODY_SIZE in node/cmd/ccq/http.go.
|
||||||
|
const signature = "";
|
||||||
|
let err = false;
|
||||||
|
await axios
|
||||||
|
.put(
|
||||||
|
QUERY_URL,
|
||||||
|
{
|
||||||
|
signature,
|
||||||
|
// bytes: Buffer.alloc(6000000).toString("hex"),
|
||||||
|
bytes: Buffer.from(serialized).toString("hex"),
|
||||||
|
},
|
||||||
|
{ headers: { "X-API-Key": "my_secret_key" } }
|
||||||
|
)
|
||||||
|
.catch(function (error) {
|
||||||
|
err = true;
|
||||||
|
expect(error.response.status).toBe(400);
|
||||||
|
expect(error.response.data).toBe(`http: request body too large\n`);
|
||||||
|
});
|
||||||
|
expect(err).toBe(true);
|
||||||
|
});
|
||||||
|
test("invalid api key with payload too large should fail based on api key", async () => {
|
||||||
|
const serialized = new Uint8Array(6000000); // Buffer should be larger than MAX_BODY_SIZE in node/cmd/ccq/http.go.
|
||||||
|
const signature = "";
|
||||||
|
let err = false;
|
||||||
|
await axios
|
||||||
|
.put(
|
||||||
|
QUERY_URL,
|
||||||
|
{
|
||||||
|
signature,
|
||||||
|
// bytes: Buffer.alloc(6000000).toString("hex"),
|
||||||
|
bytes: Buffer.from(serialized).toString("hex"),
|
||||||
|
},
|
||||||
|
{ headers: { "X-API-Key": "some_junk" } }
|
||||||
|
)
|
||||||
|
.catch(function (error) {
|
||||||
|
err = true;
|
||||||
|
expect(error.response.status).toBe(403);
|
||||||
|
expect(error.response.data).toBe(`invalid api key\n`);
|
||||||
|
});
|
||||||
|
expect(err).toBe(true);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue