-
Notifications
You must be signed in to change notification settings - Fork 82
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
KBS | Refactoring the codebase / update config file format / bring in plugin mechanism #514
base: main
Are you sure you want to change the base?
Conversation
This refactoring combines all RCAR (attestation) related code into one module. This would help to better modularization and error handling. Signed-off-by: Xynnn007 <[email protected]>
Actually, the ITA token and CoCo Token are both JWTs. They both need a JWK to verify the JWT. The difference is the way to gather the JWK. This commit combined the two logic, and add two ways to get the JWK. 1. From the configured JwkSet when launching KBS 2. From the JWT's Header's jwk field. The two ways will check the jwk endorsement in different ways. The first way is to configure the trusted JwkSet from the config. The second way is to configure the trusted CA in config. Then get the public key cert chain from Jwk's x5c field. The both ways are also supported in this patch. Rust does not provide a mature crate to verify cert chain, thus openssl is used in this patch. We also abondon rustls and openssl feature of KBS because openssl is by default used. Then we use openssl by default to make the code base simpler. Signed-off-by: Xynnn007 <[email protected]>
.map_err(|e| Error::RcarAttestFailed { source: e }) | ||
} | ||
|
||
async fn __attest( |
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.
this convention of creating "private functions w/ underscore prefixes doesn't look like idiomatic rust. can we inline __myfunc()
in myfunc()
or if they need to be separated can we put it into a module?
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.
Do you mean to use #[inline]
upon pub async fn attest(...)
?
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.
no, just move the __attest code into the attest function (unless there is a reason to keep them apart, then we can move it it into a module maybe)
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.
Oh. The wrapping is for error conversion. If move all logic from __attest
to attest
, we need a lot of map_err
from anyhow::Error
to Error
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.
if Error
is using thiserror
macros, we should be able to use a Misc(#[from] anyhow::Error)
entry in the error enum, or it even works automatically, I don't remember
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.
Yes. The #[from]
could automatically convert ALL anyhow:Error
to Error
with ?
. In this case we want to do some classfication upon the different anyhow::Error
due to Auth
and Attest
of RCAR
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.
ok, if we want to mix anyhow and concrete error types we have to resort to gymnastics like this function wrapping. If we bother about the concrete errors I'd suggest declaring them explicitly in an error enum, at least in new code. so instead of
request.cookie(KBS_SESSION_ID).context("cookie not found")?;
#[error("cookie not found")]
CookieNotFound,
...
request.cookie(KBS_SESSION_ID).ok_or(Error::CookieNotFound)?;
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.
Right. My original aim is to avoid too many error types. Overall, they are only RCAR attest failed
, because of an anyhow::Error
and RCAR auth failed
, because of an anyhow::Error
. The concrete error inside could be all coverted by anyhow
. I like the concise error type definition style myself
54c5b4d
to
bddfcf5
Compare
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.
Great work. Lots of positive changes here.
I mention one problem in a couple of my comments. Let me expand on it here.
For some of our endpoints, we have GET
and POST
functionality. The GET
request is usually called by the client in the guest, but the POST
request has too different cases. Sometimes the POST
request is a privileged admin function that is called by the kbs-client
running in a trusted environment. Sometimes it is called by the client in the guest.
In some cases we could actually support both. For resources and plugins, for instance, we might want to have an admin be able to configure the resource/plugin, but also have the guest be able to make a POST
request. Unfortunately we only have one endpoint for these two things. Maybe we should decide on a convention for how these two different things can be implemented.
I think this will also help resolve some confusion around the so-called client
and admin
APIs, which are currently intermixed.
kbs/src/api_server.rs
Outdated
} | ||
|
||
/// Client APIs. /kbs/v0/XXX | ||
pub(crate) async fn client( |
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.
Client is sort of a confusing name for this since we have APIs that can be used by the CDH but also we have the APIs to set the policy.
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.
Yes. You are right. I want to move the set_policy, set_resource APIs to kbs/v0/admin/...
. Thus we could have a distinguished barrier from client api and admin api. Maybe we can relatively change the logic of kbs-client. This change will not influence anything on the guest-components side. wdyt?
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 with me although I'm not sure it's entirely RESTful
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.
Nice catch for the RESTful design and client/admin APIs. Maybe I went to the wrong design way at first. We might only need one plugin system, and adds admin_auth
to the handle()
. This means it is the API that decides whether admin auth needs to be checked.
In this way, we can avoid extra kbs/v0/admin/<plugin-name>
, but only have one API kbs/v0/<plugin-name>
.
Wdyt?
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.
Yeah the problem with that is that you can't implement a plugin that has both GET
/ POST
for the guest and GET
/ POST
for an admin, which some people might want to do (although note that posting from the guest is a bit risky since the requests aren't encrypted).
It looks like having an /admin
route is somewhat common so maybe that's ok. Honestly I don't have a strong preference here.
kbs/src/plugins/sample.rs
Outdated
_path: String, | ||
_method: &Method, | ||
) -> Result<HttpResponse> { | ||
let response = HttpResponse::Ok().body("sample plugin response"); |
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.
Can you expand this example slightly to have different logic for GET
and POST
and to verify the admin credentials for the POST
case.
btw I wonder if we should implement the resource backend as a plugin now |
@fitzthum Thanks for the review and nice suggestion. About admin and client api, my idea is to move old admin APIs like POST btw, about the policy filter for APIs. Now it still works fine with legacy ways, s.t. GET resources. I want to also do a re-design for the policy engine module when refactoring the code. But it seems like an independent work thus not included inside this PR. Mainly for
|
bddfcf5
to
91b69d0
Compare
This commit does some refactoring upon policy engine module. Including 1. Change ResourcePolicyError to PolicyEngineError. This is because in future, different client plugins would share same policy engine thus the new name will match better. 2. add a new `set_policy` api for PolicyEngine. This api will handle SetPolicyInput format request rather than the plaintext of policy. This would help to integrate into the KBS server. The plugin mechanism is by default enabled, thus we delete `opa` and `policy` feature. By default integrate `regorus` crate for policy. Signed-off-by: Xynnn007 <[email protected]>
This module brings all admin authentication logic together. Currently it allows to use a public key to verify the admin access. Signed-off-by: Xynnn007 <[email protected]>
The resource module brings all resource storage logic together thus helps modularization. Also, it changes both `read_secret_resource` and `write_secret_resource` to Fn rather than FnMut. This leaves the synchronization handling to concrete underlying plugins, thus promote the performance because we can avoid a global Mutex. Signed-off-by: Xynnn007 <[email protected]>
The Plugins module could provide a plugin way for developers to extend the ability of KBS client APIs. This also provides a Sample implementation for example. Signed-off-by: Xynnn007 <[email protected]>
This is mostly a refactoring patch for KBS. It brings API serving into one function, and will perform different sub-function due to the requested plugin name. This also changes all configuration codes to have a default value. This patch would have some compatibility issue as it changes the old configuration format. The old configuration format is not well classified. This patch tidies the configuration items. Signed-off-by: Xynnn007 <[email protected]>
This patch fixes example configurations of KBS inside this codebase. Also, it fixes the CI test. Signed-off-by: Xynnn007 <[email protected]>
Signed-off-by: Xynnn007 <[email protected]>
Now the KBS could be built with support for all backend ASes and enable one of them runtimely due to configuration file. Signed-off-by: Xynnn007 <[email protected]>
Signed-off-by: Xynnn007 <[email protected]>
91b69d0
to
db46ea2
Compare
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 checked 542f64c only. Can that be split to a PR of its own? it's a bit too much content in one PR.
Jwk, | ||
|
||
#[serde(rename = "ITA")] | ||
Ita, |
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.
this shouldn't be a user facing name/option.
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 name do you prefer to use?
#[derive(Deserialize, Debug, Clone, PartialEq, Default)] | ||
pub struct AttestationTokenVerifierConfig { | ||
#[serde(default)] | ||
pub attestation_token_type: AttestationTokenVerifierType, | ||
pub r#type: AttestationTokenVerifierType, | ||
|
||
/// Trusted Certificates file (PEM format) path (for "CoCo") or a valid Url | ||
/// (file:// and https:// schemes accepted) pointing to a local JWKSet file | ||
/// Trusted Certificates file (PEM format) paths use to verify Attestation | ||
/// Token Signature. | ||
#[serde(default)] | ||
pub trusted_certs_paths: Vec<String>, | ||
|
||
/// Urls (file:// and https:// schemes accepted) pointing to a local JWKSet file | ||
/// or to an OpenID configuration url giving a pointer to JWKSet certificates | ||
/// (for "Jwk") to verify Attestation Token Signature. | ||
#[serde(default)] | ||
pub trusted_certs_paths: Vec<String>, | ||
pub trusted_jwk_sets: Vec<String>, | ||
|
||
/// Whether a JWK that directly comes from the JWT token is allowed to verify | ||
/// the signature. This is insecure as it will not check the endorsement of | ||
/// the JWK. If this option is set to false, the JWK will be looked up from | ||
/// the key store configured during launching the KBS with kid field in the JWT, | ||
/// or be checked against the configured trusted CA certs. | ||
#[serde(default = "bool::default")] | ||
pub insecure_key: bool, |
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 tried hard not to break users then Jwk
type was added. I don't see these changes absolutely necessary either. how would users know when to use trusted_certs_pathsor
trusted_jwk_sets`? IMO, for a proper token verifier unification, we don't have a "type" at all and users would only configure their trust sources.
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.
This is hard to do. Like I said in #519 (comment). Currently KBS will receive JWTs from either CoCoAS or ITA. I have no good idea to detect which concrete JWT is runtimely. So the design here is to configure the token type in the config file.
In future, I wonder if we can change the authentication way here. That means
- Attestation module would be responsible to interactive with backend attestation services and get a
yes/no
result. - KBS will provision a certificate that certs the tee public key and an id inside due to previous
yes/no
result.
In this way, the storage and other plugins will only need to check the endorsement of the client certificate and the id. Then the policy engine (OPA) would do much easier job -- control API access due to the client id, rather than complex different attestation claims.
I think this is good. Maybe we should also have a generic One other thing, do you think we should |
|
||
pub struct Sample { | ||
_item: 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.
Can you update the kbs-client
to take requests for plugins (e.g. Sample plugin)?
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.
Yes we can! This would give a good implementation example
kbs/src/api_server.rs
Outdated
})?; | ||
let body = body.to_vec(); | ||
let response = plugin | ||
.handle(body, query.into(), sub_path.into(), request.method()) |
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 the plugin may/should want to encrypt the response payload. If so, the handle()
should be able to access the TEE public key and call jwe()
. You could pass the TEE public key via parameter.
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.
Nice idea.
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 added a encrypted()
method for plugin, that will tell KBS if the response needs to be encrypted via JWE.
Resource backend is somehow different. If we want to implement it as a backend plugin, we would use a config item like ...
[[plugins]]
name = "ResourceStorage"
type = "LocalFs"
dir_path = "/tmp/kbs-resource" While what we are using now is ...
[repository]
type = "LocalFs"
dir_path = "/tmp/kbs-resource" The difference
|
Signed-off-by: Xynnn007 <[email protected]>
@fitzthum I added one commit that tries to make |
I really like moving the resource stuff to a plugin. I think it makes sense to get rid of the resource feature. In the future there might be some complex plugins that are behind feature flags (in addition to being enabled at runtime), but imo we should always build the resource support. Like I said, I could go either way on the admin routes. Maybe another reviewer has an opinion there. Otherwise I think this is good. |
Nice. I wonder if @cclaudio could give some suggestions upon the design.
I added #524 about Token parts. Hopefully that makes sense and get merged. Then we can go back to this PR to do some tidy work. |
This PR includes the following work
I am sorry to have such a huge PR and did some disruptive changes. I originally want to add a simple plugin mechanism but soon found that some architectural problems and hidden bugs, thus fixed them by the way. At last resulting such many changed lines.
This PR still works fine with kbs-client on the guest-components side and did not change the protocol.
Needs some attestion from ITA folks @mythi @jodh-intel to see if the changes upon ITA config need any change.
Nebula folks @cclaudio about #451
cc @fitzthum @jialez0 @mkulke
Fixes #486
Related to #502