Enforce MD5 auth string invariants via new type#792
Conversation
Intoduces Md5AuthString type to wrap a String behind a fallible constructor and immutable getters. This ensures that an API client is unable to configure an MD5 auth key that does not comply with the inputs recommended by RFC 2385 (TCP MD5 option for BGP): ``` 4.5 Key configuration It should be noted that the key configuration mechanism of routers may restrict the possible keys that may be used between peers. It is strongly recommended that an implementation be able to support at minimum a key composed of a string of printable ASCII of 80 bytes or less, as this is current practice. ``` The Illumos and Linux kernel implementations both accept 80-byte keys with arbitrary contents, so the printable ASCII constraint is purely coming from RFC 2385 and the need for keys to round-trip through json via OpenAPI / Dropshot. Fixes: #765 Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
e5f8845 to
7367dc4
Compare
|
The libnet dependency is also bumped in this ticket in order to pull in oxidecomputer/netadm-sys#210. This gives us an additional layer of checking around the MD5 auth keys we submit to the kernel. |
| }] | ||
| async fn read_unnumbered_neighbors_v8( | ||
| rqctx: RequestContext<Self::Context>, | ||
| request: Query<latest::bgp::config::AsnSelector>, |
There was a problem hiding this comment.
Why this change? AFAICT latest::bgp::config::AsnSelector is an alias for v1::bgp::config::AsnSelector.
There was a problem hiding this comment.
I believe this is consistent with the API migration guide, specifically https://github.com/oxidecomputer/dropshot-api-manager/blob/main/guides/new-version.md#for-changed-and-removed-endpoints. Since this endpoint has been removed, its input/output types should never change; they're effectively pinned to whatever latest was at the point at which they were removed.
If latest is still an alias to v1 in this case either will compile and pass the drift checks, but leaving latest in a removed endpoint is a bit of a landmine for future maintainers: if someone changes AsnSelector in the future, they now have to go back to all the API endpoints currently using latest and pin them to the previous-latest version by vN.
There was a problem hiding this comment.
The _vX suffixed methods exist to handle requests targeting old versions of an API endpoint. Latest is a live target, which means the underlying type could change in a way that affects the behavior of the API handler without causing a compilation failure. I moved these handlers for old versions over to using a pinned version of the types to avoid future issues if this were to change. I believe this should have been done during the RFD 619 migration but was missed due to the size of that change. I didn't bother breaking it out into its own PR because it didn't seem like a large amount of clutter, but I'm happy to do so if you'd prefer that.
There was a problem hiding this comment.
I think John and I were typing at the same time. I would say that where we want to start exercising judgement here is when to retire old API versions.
Afaik, the only consumers of mg-admin-client are nexus, mgadm, falcon-lab and possibly a couple spots in the testbed repo (a4x2 or interop). There's really no reason we need to maintain 10+ API versions when all the clients are under our purview
| async fn read_unnumbered_neighbor_v8( | ||
| rqctx: RequestContext<Self::Context>, | ||
| request: Query<latest::bgp::config::UnnumberedNeighborSelector>, | ||
| request: Query<v5::bgp::config::UnnumberedNeighborSelector>, |
There was a problem hiding this comment.
Same question here, why change from the latest alias to the explicitly numbered version?
| .into() | ||
| let latest = Neighbor::from_rdb_neighbor_info(rq.asn, &x); | ||
| let v11: v11::bgp::config::Neighbor = latest.into(); | ||
| v8::bgp::config::Neighbor::from(v11).into() |
There was a problem hiding this comment.
This is rather confusing code to read. Constructing a v11 Neighbor to turn into a v8 neighbor to then turn into a v1 neighbor for the return object. It seems like there should be From/Into impls that take care of conversion chains so conversions don't clutter up handler code.
There was a problem hiding this comment.
The general guidance from https://github.com/oxidecomputer/dropshot-api-manager/blob/main/guides/new-version.md#add-conversions-to-or-from-the-immediately-prior-version is to only define a conversion from the most recent version being replaced, but I think based on the end of that section it assumes we're usually only converting 1 step at a time in the API trait:
In general, don't add From impls from other prior versions. (So, if a type changed from v1 to v4 to v9, avoid implementing conversions from v9 to v1 or vice versa.) Instead, hop through intermediate versions in the API trait. In some cases it may be more efficient to have direct conversions to prior versions; use appropriate judgment.
I assume we have this read_neighbors_v1 because converting in the API trait isn't tenable for some reason? So maybe this is a case where appropriate judgment is to define conversions for a longer chain.
There was a problem hiding this comment.
I'm pretty sure RFD 619 says to only impl conversions between the newly introduced type version and the previous version type. The handlers for the legacy API versions would then carry the burden for walking the conversion chain since Rust can't just call .into() and have it implicitly use multiple From/Into conversions in one shot.
Edit: I think John and I were typing at the same time again
There was a problem hiding this comment.
Why don't we just implement this handler as something like:
#[endpoint {
method = GET,
path = "/bgp/config/neighbors",
operation_id = "read_neighbors",
versions = ..VERSION_MP_BGP,
}]
async fn read_neighbors_v1(
rqctx: RequestContext<Self::Context>,
request: Query<v1::bgp::config::AsnSelector>,
) -> Result<HttpResponseOk<Vec<v1::bgp::config::Neighbor>>, HttpError> {
Self::read_neighbors_v4(rqctx, request)
.await
.map(|r| r.map(|v| v.into_iter().map(Into::into).collect()))
}in mg-api/src/lib.rs like the other older versions of this function? Similar for the other _v1 impls.
There was a problem hiding this comment.
I find this is harder to parse than the current code, but I don't have strong opinions about the style here. Happy to go this way if it's something you want changed though
There was a problem hiding this comment.
It's about more than style, it limits blast radius of endpoint interoperability complexity to just the API trait and From/Into impls that only have to operate across a single version bump.
There was a problem hiding this comment.
I think I see what you mean now. I'll push another commit to tidy this up
Makes a bunch of the version-suffixed API endpoint handlers into default methods. Updates the calling convention for all the different versioned endpoint handlers such that they each call the next latest version and convert via .into(). Also updates the bgp_apply path to properly walk the conversion path v1 -> v4 -> v8 instead of skipping v4. Also updates the bgp_apply group removal test to call do_bgp_apply with latest rather than using a conversion chain. Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
Signed-off-by: Trey Aspelund <trey@oxidecomputer.com>
Intoduces Md5AuthString type to wrap a String behind a fallible
constructor and immutable getters. This ensures that an API client is
unable to configure an MD5 auth key that does not comply with the
inputs recommended by RFC 2385 (TCP MD5 option for BGP):
The Illumos and Linux kernel implementations both accept 80-byte keys
with arbitrary contents, so the printable ASCII constraint is purely
coming from RFC 2385 and the need for keys to round-trip through json
via OpenAPI / Dropshot.
Fixes: #765