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

RPC provider #343

Merged
merged 27 commits into from
Sep 13, 2023
Merged

RPC provider #343

merged 27 commits into from
Sep 13, 2023

Conversation

jacobweinstock
Copy link
Member

What does this PR implement/change/remove?

This adds an RPC provider. The idea of the RPC provider is to enable users the ability to interoperate with existing bespoke out of band system or as a stop gap for BMCs/protocols that bmclib does not have a provider for yet. Internal bespoke out of band systems will generally not be accepted into BMCLIB.

So users can run a small web endpoint that implements the RPC contract. The RPC provider sends a request to the user's web endpoint. The web endpoint will then translate the call to the out of band protocol/tool/etc that is needed to communicate with the BMC.

Checklist

  • Tests added
  • Similar commits squashed

The HW vendor this change applies to (if applicable)

The HW model number, product name this change applies to (if applicable)

The BMC firmware and/or BIOS versions that this change applies to (if applicable)

What version of tooling - vendor specific or opensource does this change depend on (if applicable)

Description for changelog/release notes

@codecov
Copy link

codecov bot commented Aug 12, 2023

Codecov Report

Patch coverage: 67.36% and project coverage change: +1.75% 🎉

Comparison is base (08172f2) 42.68% compared to head (61c88e3) 44.43%.
Report is 1 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #343      +/-   ##
==========================================
+ Coverage   42.68%   44.43%   +1.75%     
==========================================
  Files          44       50       +6     
  Lines        3730     4015     +285     
==========================================
+ Hits         1592     1784     +192     
- Misses       1951     2027      +76     
- Partials      187      204      +17     
Files Changed Coverage Δ
option.go 20.00% <0.00%> (-1.16%) ⬇️
providers/rpc/experimental.go 0.00% <0.00%> (ø)
client.go 55.55% <7.14%> (-3.52%) ⬇️
providers/rpc/payload.go 50.00% <50.00%> (ø)
providers/rpc/http.go 56.75% <56.75%> (ø)
providers/rpc/signature.go 69.38% <69.38%> (ø)
providers/rpc/rpc.go 77.46% <77.46%> (ø)
providers/rpc/logging.go 100.00% <100.00%> (ø)

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@jacobweinstock jacobweinstock marked this pull request as ready for review September 1, 2023 19:25
@jacobweinstock jacobweinstock changed the title [WIP] RPC provider RPC provider Sep 1, 2023
providers/rpc/rpc.go Outdated Show resolved Hide resolved
providers/rpc/doc.go Show resolved Hide resolved
providers/rpc/experimental.go Outdated Show resolved Hide resolved
providers/rpc/rpc.go Outdated Show resolved Hide resolved
providers/rpc/rpc.go Outdated Show resolved Hide resolved
providers/rpc/http.go Outdated Show resolved Hide resolved
client.go Show resolved Hide resolved
Copy link
Collaborator

@chrisdoherty4 chrisdoherty4 left a comment

Choose a reason for hiding this comment

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

Nice to see this shaping up.

I noticed there's a fair amount of flexibility and I'm not sure I appreciate why. For example, there are options to specify the HTTP content type and HTTP method used: whats the motivation for being so flexible when we are ultimately defining the contract?

In the case of content type, given we assume JSON does it make sense to even expose that in such a raw manner? (this makes me wonder if I really understand whats going on 🤔).

providers/rpc/rpc.go Show resolved Hide resolved
providers/rpc/rpc.go Show resolved Hide resolved
providers/rpc/rpc.go Outdated Show resolved Hide resolved
providers/rpc/rpc.go Show resolved Hide resolved
providers/rpc/rpc.go Outdated Show resolved Hide resolved
providers/rpc/payload.go Outdated Show resolved Hide resolved
providers/rpc/payload.go Outdated Show resolved Hide resolved
c.Logger.Info("rpc notification details", kvs...)
}
}()
defer resp.Body.Close()
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't recall how defer works but are you sure this closes the right handle given we reset the body on l.74?

(it may be the case that Go stores the point in memory it'll jump to on return at the time of defer, hence I'm not sure if this is a problem)

Copy link
Member Author

Choose a reason for hiding this comment

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

As im passing in resp *http.Response, i am already handling the close in the caller so i think i will just remove this Close. Thoughts?

providers/rpc/rpc.go Show resolved Hide resolved
providers/rpc/rpc.go Outdated Show resolved Hide resolved
@jacobweinstock
Copy link
Member Author

Nice to see this shaping up.

I noticed there's a fair amount of flexibility and I'm not sure I appreciate why. For example, there are options to specify the HTTP content type and HTTP method used: whats the motivation for being so flexible when we are ultimately defining the contract?

In the case of content type, given we assume JSON does it make sense to even expose that in such a raw manner? (this makes me wonder if I really understand whats going on 🤔).

Thanks for the review, Chris.

whats the motivation for being so flexible when we are ultimately defining the contract?

With the goal of the RPC provider being to provide interoperability with existing/bespoke out-of-band systems, for me, flexibility is key. POST vs PATCH or even a GET for the HTTP method will provide that flexibility while not compromising our payload contracts. The content type, while we will always send json, could have a requirement in the existing system. i.e. application/vnd.api+json or application/json or some other custom type.

@chrisdoherty4
Copy link
Collaborator

@jacobweinstock Sounds good, thanks for the explanation.

providers/rpc/http.go Outdated Show resolved Hide resolved
Comment on lines 326 to 332
// handle the response
rp, err := c.handleResponse(resp, kvs)
if err != nil {
return ResponsePayload{}, err
}

return rp, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// handle the response
rp, err := c.handleResponse(resp, kvs)
if err != nil {
return ResponsePayload{}, err
}
return rp, nil
// handle the response
return c.handleResponse(resp, kvs)

The idea of the RPC provider is to enable
users the ability to interoperate with existing
bespoke out of band system or as a stop gap for
BMCs/protocols that bmclib does not have a provider
for yet. Internal bespoke out of band systems will
generally not be accepted into BMCLIB.

So users can run a small web endpoint that
implements the RPC contract. The RPC provider sends
a request to the user's web endpoint. The web endpoint
will then translate the call to the out of band
protocol/tool/etc that is needed to communicate with the BMC.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
Hopping to help with understanding of the code base.

Signed-off-by: Jacob Weinstock <[email protected]>
Reorg the Config struct to make clearer
the different areas of concern. Remove
internal package.

Signed-off-by: Jacob Weinstock <[email protected]>
This doc.go will detail the why of
this package and its use.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
This was just experimentation.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
As bespoke out-of-band systems will require
a high level of flexibility to interoperate
properly, this provides even more flexibility
around the RequestPayload while still maintaining
a simple contract for the bmclib.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
Improvements for cleaner/clearer code.

Signed-off-by: Jacob Weinstock <[email protected]>
This is more core to the provider than
the request. Used to submit the request
not configure the request, so to speak.

Signed-off-by: Jacob Weinstock <[email protected]>
Rename interface{} -> any. This change
is compatible back to Go 1.18.

Signed-off-by: Jacob Weinstock <[email protected]>
The struct name was changed so this
updates the receiver variable to match.

Signed-off-by: Jacob Weinstock <[email protected]>
Clarify its use with an example.

Signed-off-by: Jacob Weinstock <[email protected]>
Let the caller handle the close.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
This guards against dos attacks that
might send very large responses.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
Check status code and response payload
in the Open method to validate the endpoint
is conformant with the response contract.

Update checking of the response error code as
well as if it is nil. This will make sure we
dont error out when a response contains a
value for the error instead of just nil.

Signed-off-by: Jacob Weinstock <[email protected]>
Signed-off-by: Jacob Weinstock <[email protected]>
Reading from io.Readers and then creating
a nop closer and dealing with closing the
readers was more difficult than it needed
to be.

Signed-off-by: Jacob Weinstock <[email protected]>
}
defer resp.Body.Close()
respBuf := new(bytes.Buffer)
if _, err := io.Copy(respBuf, resp.Body); err != nil {
Copy link
Collaborator

@chrisdoherty4 chrisdoherty4 Sep 12, 2023

Choose a reason for hiding this comment

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

I think this may still be at risk of a DoS because Copy will read everything from Body into the buffer. That could consume a CPU and chew threw all available memory.

I think the only way to safely read is to chunk (https://pkg.go.dev/io#CopyN) checking the total bytes read as we go.

Copy link
Member Author

Choose a reason for hiding this comment

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

ah yeah, fair. i'll use CopyN and move the copy below the content length check.

Moving the response body copy to after the
content length check and only coping the
resp.Contentlegnth should give us more
defense against malicious actors.

Signed-off-by: Jacob Weinstock <[email protected]>
Copy link
Member

@joelrebel joelrebel left a comment

Choose a reason for hiding this comment

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

Thanks for the efforts here 🚀

@mergify mergify bot merged commit b1092ac into bmc-toolbox:main Sep 13, 2023
7 checks passed
@jacobweinstock jacobweinstock deleted the rpc-provider branch September 13, 2023 21:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants