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

feat: Add All or Nothing Bearer token auth support #24666

Closed
wants to merge 2 commits into from

Conversation

mgattozzi
Copy link
Contributor

This commit adds basic authorization support to Edge. Up to this point we didn't need have authorization at all and so the server would receieve and accept requests from anyone. This isn't exactly secure or ideal for a deployment and so we add a basic form of authentication.

The way this works is that a user passes in a hex encoded sha256 hash of a given token to the '--bearer-token' flag of the serve command. When the server starts with this flag it will now check a header of the form 'Authorization: Bearer ' by making sure it is valid in the sense that it is not malformed and that when token is hashed it matches the value passed in on the command line. The request is denied with either a 400 Bad Request if the header is malformed or a 401 Unauthorized if the hash does not match or the header is missing.

The user is provided a new subcommand of the form: 'influxdb3 create token ' where the output contains the command to run the server with and what the header should look like to make requests.

I can see future work including multiple tokens and rotating between them or adding new ones to a live service, but for now this shall suffice.

As part of the commit end-to-end tests are included to run the server and make requests against the HTTP API and to make sure that requests are denied for being unauthorized, accepted for having the right header, or denied for being malformed.

Also as part of this commit a small fix is included for 'Accept: /' headers. We were not checking for them and if this header was included we were denying it instead of sending back the default payload return value.

Closes #24646

This commit adds basic authorization support to Edge. Up to this point
we didn't need have authorization at all and so the server would
receieve and accept requests from anyone. This isn't exactly secure or
ideal for a deployment and so we add a basic form of authentication.

The way this works is that a user passes in a hex encoded sha256 hash of
a given token to the '--bearer-token' flag of the serve command. When
the server starts with this flag it will now check a header of the form
'Authorization: Bearer <token>' by making sure it is valid in the sense
that it is not malformed and that when token is hashed it matches the
value passed in on the command line. The request is denied with either a
400 Bad Request if the header is malformed or a 401 Unauthorized if the
hash does not match or the header is missing.

The user is provided a new subcommand of the form: 'influxdb3 create
token <token>' where the output contains the command to run the server
with and what the header should look like to make requests.

I can see future work including multiple tokens and rotating between
them or adding new ones to a live service, but for now this shall
suffice.

As part of the commit end-to-end tests are included to run the server
and make requests against the HTTP API and to make sure that requests
are denied for being unauthorized, accepted for having the right header,
or denied for being malformed.

Also as part of this commit a small fix is included for 'Accept: */*'
headers. We were not checking for them and if this header was included
we were denying it instead of sending back the default payload return
value.
Copy link
Contributor

@hiltontj hiltontj left a comment

Choose a reason for hiding this comment

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

The authentication part in the server looks good and tests are solid, but I put in a couple of comments there.


Otherwise, I have some ideas for the CLI component that I can write up on the related issue #24649 .

// Test that there is an extra string after the token foo
match client
.get("http://127.0.0.1:8181/api/v3/query_sql?db=foo&q=select+*+from+cpu")
.header("Authorization", "Bearer foo whee")
Copy link
Contributor

Choose a reason for hiding this comment

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

clap might prevent this but could the user not enter a token that has whitespace in it?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does not seem to prevent this if you pass the arg "". I will change it to check for the empty string

Comment on lines 36 to 52
match client
.post("http://127.0.0.1:8181/api/v3/write_lp?db=foo")
.body("cpu,host=a val=1i 123")
.send()
.await
{
Err(e) => {
server.kill().unwrap();
panic!("request errored out: {e}");
}
Ok(resp) => {
if resp.status() != StatusCode::UNAUTHORIZED {
server.kill().unwrap();
panic!("status code was not unauthorized: {:#?}", resp);
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

At the sacrifice of debuggable output, you could probably condense your assertion code by using the form:

assert_eq!(
    401,
    client
        .post("...")
        .body("...")
        .send()
        .await
        .status()
        .as_u16(),
    "status code was not unauthorized",
);

But then, you might also need a wrapper type to hold your server, so that it can kill the std::process::Command on Drop or something like that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah. The other problem is that if panic=abort it'll just skip this and it'll cause the program to remain open. I don't think we are and so it could be condensed, but I opted for the more verbose one in this case. I can experiment with a Drop wrapper to see if it works though!

@mgattozzi
Copy link
Contributor Author

@hiltontj I made the changes to error on an empty string and with some code contortion got the code for assertions to be better and in the image below it shows that the server doesn't continue to run even if there is a panic

Screenshot from 2024-02-20 13-03-51

@mgattozzi mgattozzi requested a review from hiltontj February 20, 2024 18:07
Copy link
Contributor

@hiltontj hiltontj left a comment

Choose a reason for hiding this comment

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

Nice, LGTM.

@mgattozzi
Copy link
Contributor Author

Uh so. Hm really weird, but I merged this via well here, BUT it failed or so I thought. It's currently in main -> de102bc

and I can pull it from the command line so uh. GitHub had a bad moment here? I'm going to close the PR, but it's actually merged. Love this. Love hitting a bug.

@mgattozzi mgattozzi closed this Feb 20, 2024
@mgattozzi mgattozzi deleted the mgattozzi/bearer-auth branch February 20, 2024 20:39
hiltontj pushed a commit that referenced this pull request Feb 20, 2024
This commit adds basic authorization support to Edge. Up to this point
we didn't need have authorization at all and so the server would
receive and accept requests from anyone. This isn't exactly secure or
ideal for a deployment and so we add a basic form of authentication.

The way this works is that a user passes in a hex encoded sha256 hash of
a given token to the '--bearer-token' flag of the serve command. When
the server starts with this flag it will now check a header of the form
'Authorization: Bearer <token>' by making sure it is valid in the sense
that it is not malformed and that when token is hashed it matches the
value passed in on the command line. The request is denied with either a
400 Bad Request if the header is malformed or a 401 Unauthorized if the
hash does not match or the header is missing.

The user is provided a new subcommand of the form: 'influxdb3 create
token <token>' where the output contains the command to run the server
with and what the header should look like to make requests.

I can see future work including multiple tokens and rotating between
them or adding new ones to a live service, but for now this shall
suffice.

As part of the commit end-to-end tests are included to run the server
and make requests against the HTTP API and to make sure that requests
are denied for being unauthorized, accepted for having the right header,
or denied for being malformed.

Also as part of this commit a small fix is included for 'Accept: */*'
headers. We were not checking for them and if this header was included
we were denying it instead of sending back the default payload return
value.
@jdstrand
Copy link
Contributor

a user passes in a hex encoded sha256 hash of a given token to the '--bearer-token' flag of the serve command

I know we're very early in all of this, but for future work whether it is through documentation or CLI tooling for generating the raw token, we should use the same process for creating tokens as for other InfluxDB 3 products:

  1. use 512 bits (64 bytes) of randomness, then base64 encode these bits
  2. prepend a prefix (in Dedicated and Clustered database tokens, this is apiv1_) and give this 'raw token' to the user (ie apiv1_<base64>)[*]
  3. hash raw token (eg, sha512(apiv1_<base64>) (sha256 is ok, but still advocating for sha512 ;). Store/use this hash server-side (and never store the raw token server-side)

By doing this, we ensure we have sufficient randomness (base64 encoded 512 bits is a fine choice and isn't unwieldy), have a consistent user experience across the product line[*] and have a consistent implementation/developer experience across the product line. Perhaps you would choose a different prefix for OSS 3.

[*] for historical reasons, InfluxDB 3 Serverless does not use a prefix (apiv1_ or otherwise).

cc @pauldix

@pauldix
Copy link
Member

pauldix commented Feb 26, 2024

@mgattozzi I think it would be good to update our token setup to be consistent with what @jdstrand mentions. So base64 encode it, prepend apiv3_ to that and then the hash is that whole blob.

@mgattozzi
Copy link
Contributor Author

Sounds good. I might not be able to get to it this week, but I can open up an issue later to keep this on our radar

@mgattozzi
Copy link
Contributor Author

Turned this into an issue -> #24704

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create bearer token all-or-nothing auth
4 participants