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
Admin interface for node operators #1222
Conversation
Codecov Report
@@ Coverage Diff @@
## master #1222 +/- ##
==========================================
+ Coverage 54.70% 54.81% +0.10%
==========================================
Files 502 504 +2
Lines 31769 31918 +149
==========================================
+ Hits 17380 17496 +116
- Misses 12026 12048 +22
- Partials 2363 2374 +11
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for getting started on the admin interface! Besides the points I made inline:
-
Authentication:
I would note we already have tools in utils/grpcutils/grpc.go for server-side uthentication: creating a gRPC server with a self-signed TLS certificate and a client that checks against the corresponding (pre-shared) PubKey. It would only require minor changes for the server to check the client against a certificate that the gRPC server would have been started with. Not that this is enough - I'd expect this to eventually require token auth - but since it's there ... -
Security:
In order to augment the predictability of this interface - incidentally limitiing opportunities for mischief: would it be possible to haveRegisterHandler|RegisterValidator
be part of the API of a CommandRunnerBootstrapper, rather than part of the runtime?
@@ -722,6 +743,10 @@ func (fnb *FlowNodeBuilder) Initialize() NodeBuilder { | |||
fnb.RegisterBadgerMetrics() | |||
} | |||
|
|||
if fnb.adminAddr != NotSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about adminAddr
at NotSet
but with a specified adminHttpAddr
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this case we would just ignore the adminHttpAddr
. I can add validation for this although I don't think it's really that necessary?
The adminHttpAddr
only makes sense if adminAddr
is set. We cannot have an http server if the underlying gRPC service doesn not even exist.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
First, I think you can serve GRPC over a Unix domain socket rather than a TCP socket (and in this context, this might actually not even be such a bad idea).
But more to my original point, you can interpret the presence of an adminHttpAddr
alone as an authorization to bind to a random port for the GRPC endpoint, especially if you document that it is the contract.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I guess we can do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually on second thought, we cannot just choose a random port because we also need to know which interface to bind to.
As I mentioned in the previous comment, I'm not really a fan of limiting the interface to localhost only.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can have a safe default, and that an address parameter that specifies, e.g. just a port, can be interpreted as a default binding to localhost.
With that said, we distribute this through Docker, which won't expose an unexported port, so at this stage it's a nit.
cmd/node_builder.go
Outdated
adminAddr string | ||
adminHttpAddr string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about making these booleans, binding only to localhost? This way we're less vulnerable to inadvertent firewall openings.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But we need some way to specify the port?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, let's specify just the port then.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like there are very valid reasons why one might want to bind to an interface other than localhost (for example, if they want the admin service to be accessible remotely).
|
In theory, I agree with you. In practice, assuming our app is always deployed in a context where a vigilant and wise sysadmin has the time to do that securization well is a non-starter, unfortunately. Moreover, the code necessary to perform this "give credential at bootup, check it later when receiving commands", is, as I mentioned, quite close by. I mean that in the current approach of the admin interface API it would be easy for a distant code component to dynamically add -or worse remove- a handler / validator to the admin interface, possibly under certain conditions. That makes it more complex to reason about whether the admin interface is exploitable, because it opens the possibility that an adversary would try to manipulate those conditions to activate or deactivate the handlers / validators it wishes. On the other hand, if handlers and validators can only be mutated at the start of the node, and not later, that reasoning is much more simple (hence the builder pattern). |
@huitseeker Okay, I agree with 2 and will make that change. I'll look into 1. I will have to enable mutual authentication right? (Reference for self: grpc/grpc-go#403) |
Essentially, yes. @vishalchangrani is quite familiar with the feature and can probably help. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice work! Thanks a lot!
} | ||
|
||
if handler := r.getHandler(command.command); handler != nil { | ||
// TODO: we can probably merge the command context with the worker context |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fnb.flags.StringVar(&fnb.BaseConfig.adminCert, "admin-cert", defaultConfig.adminCert, "admin cert file (for TLS)") | ||
fnb.flags.StringVar(&fnb.BaseConfig.adminKey, "admin-key", defaultConfig.adminKey, "admin key file (for TLS)") | ||
fnb.flags.StringVar(&fnb.BaseConfig.adminClientCAs, "admin-client-certs", defaultConfig.adminClientCAs, "admin client certs (for mutual TLS)") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably open an issue for the convenience of auto-generating this certificate pair (the generation would happen at boot, for a later use).
Auto-generation may fit the needs of e.g. a partner running a single node, whereas we'd be more interested in passing our own cert, to manage a group of nodes with the same creds.
@@ -722,6 +743,10 @@ func (fnb *FlowNodeBuilder) Initialize() NodeBuilder { | |||
fnb.RegisterBadgerMetrics() | |||
} | |||
|
|||
if fnb.adminAddr != NotSet { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we can have a safe default, and that an address parameter that specifies, e.g. just a port, can be interpreted as a default binding to localhost.
With that said, we distribute this through Docker, which won't expose an unexported port, so at this stage it's a nit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lgtm
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM. great work
bors merge |
1222: Admin interface for node operators r=smnzhu a=smnzhu closes https://github.com/dapperlabs/flow-go/issues/5818 ### TODO - [x] Write tests - [x] Document the protobuf file - [x] Provide a rest interface using grpc Gateway Co-authored-by: Simon Zhu <simon.zsiyan@gmail.com>
Build failed: |
bors merge |
1222: Admin interface for node operators r=smnzhu a=smnzhu closes https://github.com/dapperlabs/flow-go/issues/5818 ### TODO - [x] Write tests - [x] Document the protobuf file - [x] Provide a rest interface using grpc Gateway Co-authored-by: Simon Zhu <simon.zsiyan@gmail.com>
Build failed: |
bors retry |
closes https://github.com/dapperlabs/flow-go/issues/5818
TODO