Skip to content
This repository has been archived by the owner on Mar 26, 2020. It is now read-only.

doc: REST API authentication #1021

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ GlusterD, while also becoming more modular and easing extensibility.
## Documentation

* [Quick Start User Guide](doc/quick-start-user-guide.md)
* [REST API Authentication](doc/rest-authentication.md)
* [Development Guide](doc/development-guide.md)
* [Coding Guidelines](doc/coding.md)
* [REST API Reference](doc/endpoints.md)
Expand Down
2 changes: 2 additions & 0 deletions doc/quick-start-user-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ Now you have two nodes running glusterd2.

> NOTE: Ensure that firewalld is configured (or stopped) to let traffic on ports ` before adding a peer.

REST API authentication is enabled by default, read more about it [here](rest-authentication.md).

## Add peer

Glusterd2 natively provides only ReST API for clients to perform management operations. A CLI is provided which interacts with glusterd2 using the [ReST APIs](https://github.com/gluster/glusterd2/wiki/ReST-API).
Expand Down
106 changes: 106 additions & 0 deletions doc/rest-authentication.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Glusterd2 REST API authentication

Glusterd2 REST API authentication is based on [JWT](https://jwt.io). If REST
authentication is enabled then each client request should include
`Authorization` header as example below.

Authorization: bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnbHVzdGVyY2xpIiwiaWF0IjoxNTMxNzI5NzQyLCJleHAiOjE1MzE3Mjk3NTJ9._WIwO7PrHUHIT62SdzfkyNjqD1GEgX2cYqN8ACZCtaw

**Note**: REST Authentication can be disabled by adding `restauth=false`
in Glusterd2 config file(Default path is
`/var/lib/glusterd2/glusterd2.toml` in case of rpm installation)

## Default user

When `glusterd2` starts for the first time, it creates the
`$GLUSTERD2_STATEDIR/auth` file which will contain the secret. If
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we document anywhere what GLUSTERD2_STATEDIR path mean? Might be good to point out to a link.

Copy link
Member

@kshlm kshlm Jul 18, 2018

Choose a reason for hiding this comment

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

Do we document anywhere what GLUSTERD2_STATEDIR path mean? Might be good to point out to a link

I don't think we have a document about the paths yet. We should do it.

Apart from that, I have a concern about using the GD2 secret by external tools.

The secret is meant to be accessible only by GD2. GD2 should be using this secret to create and handout tokens. Having the clients access the secret is a big security risk. Later in the document, we ask that the secret be copied out externally and have external client generate their own token. This is a serious lapse of security. Because the secret can be passed along to unintended users and those users would have full access to the GD2 cluster.

Ideally, as I mentioned earlier GD2 should be the one generating and handing out tokens. Even for glustercli it would be preferrable that you use token. GD2 should generate a token for CLI and save it to a known place. And we should add a way to generate tokens to GD2 (ReST or CLI to be decided).

Sorry about bringing this up now. I should have been paying attention when the actual PR was being reviewed. I'll open up a new issue about glustercli not using the secret file directly. We can merge this PR without waiting for that, but only if it is modified to address my comments later on.

"gluster" user group is available in the system then this `auth` file
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we mention how to create "gluster" user group?

Copy link
Member

Choose a reason for hiding this comment

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

I don't think we need to do this. This is simple unix knowledge. In any case, if installed from packages, the package should take care of creating the users/groups. I don't think any gluster packages do this yet.

Copy link
Contributor

Choose a reason for hiding this comment

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

It may be worthwhile to point out this is a unix group interacting with file permissions and not some special gluster thing in that case. The word group and phrase "in that machine" alone may not hint at that strongly enough.

can be read by any user in that machine who are part of "gluster"
group.

**Note**: If "gluster" user group is not available during first start
of `glusterd2` it limits the read permission to `root:root`

## glustercli

With default installation, `glustercli` will know the location of
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps, "The glustercli command knows the default location of the auth file." would read more clearly.

`auth` file. `glustercli` will generate JWT token using the secret
available in `auth` file and attach it with every REST API calls.
Copy link
Contributor

Choose a reason for hiding this comment

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

"every REST API call." or "all REST API calls."


Default installation will not require any change to use `glustercli`.

If `auth` file is in different path(When `glusterd2` is running with
Copy link
Contributor

Choose a reason for hiding this comment

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

If the auth file is in a different path (for example, when glusterd2 is running with a custom workdir)...

custom `workdir`), then run `glustercli` by specifying
`--secret-file`. For example,

glustercli --secret-file=/root/setup1/glusterd2/auth peer status

**Note**: bash alias can be added like `alias glustercli='glustercli
--secret-file=/root/setup1/glusterd2/auth`
Copy link
Contributor

Choose a reason for hiding this comment

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

Do you think it might be a little out of place to teach people about bash aliases in the doc?


Secret is taken by `glustercli` in following order of precedence
(highest to lowest):

--secret
--secret-file
GLUSTERD2_AUTH_SECRET (environment variable)
--secret-file (default path)
Copy link
Contributor

Choose a reason for hiding this comment

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

Putting --secret-file here again seems confusing from a user perspective IMO. Just don't mention it and say "default path". It may play into the implementation but I don't think that's relevant to the person reading the doc.


## Curl example
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be under ReST API section?

Copy link
Member

Choose a reason for hiding this comment

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

A link should be added from the ReST API document to this doc.


Download the utility script `glustercli-auth-header.py` from
[here](https://github.com/gluster/glusterd2/tree/master/pkg/tools/)
and save it in server nodes.

Add alias in `~/.bashrc` as below,

alias gcurl='curl -H "$(python3 ~/glustercli-auth-header.py --secret-file=/var/lib/glusterd2/auth)"'

**Note**: Change the path of script and auth file to match your setup.

Thats all, use `gcurl` wherever `curl` is necessory. For example, to start a Volume
Copy link
Contributor

Choose a reason for hiding this comment

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

necessary not necessory


gcurl -XPOST http://localhost:24007/v1/volumes/gv1/start

## Python example

Install `jwt` library using `pip install jwt` or using `dnf install
python-jwt`. Generate JWT token using,

import jwt
Copy link
Contributor

Choose a reason for hiding this comment

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

It might make sense to enclose this block with ````python` esp. if its expected this will be viewed using github/github-style-markdown-renderer.

import requests

user = "glustercli"
secret_file = "/var/lib/glusterd2/auth"
secret = open(secret_file).read()

claims = {
"iss": "glustercli",
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(seconds=10)
}

token = jwt.encode(claims, secret, algorithm='HS256')
resp = requests.get("http://localhost:24007/v1/peers",
headers={"Authorization": "bearer " + token}

peers = []
if resp.status_code == 200:
peers = json.loads(resp.content)


## Using REST APIs from outside the Cluster nodes

Copy link
Member

Choose a reason for hiding this comment

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

I'd prefer you remove this section completely for now. Or at least modify such that it doesn't involve copying out the secret file. The glustercli example would need to be completely removed. For the curl example, you should change it so that the python script is used to generate a token on one of peers, and the token is copied over.

Copy link
Member Author

Choose a reason for hiding this comment

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

makes sense. I updated the same in other comment #1021 (comment)

For the curl example, you should change it so that the python script is used to generate a token on one of peers, and the token is copied over.

We are following shared secret approach. No change required for curl example unless we decide to do shared token approach again.

APIs for creating external users is not yet implemented. Temporarily
copy the `auth` file from one of the server nodes and place it in a
secured location.(Say `~/.glustercli/auth`)

Run `glustercli` by specifying `--secret-file`. For example,

glustercli --secret-file=~/.glustercli/auth peer status

Or use `curl` as below

curl -H "$(python3 ~/glustercli-auth-header.py --secret-file=~/.glustercli/auth)" \
http://gluster1.example.com:24007/v1/peers

33 changes: 33 additions & 0 deletions pkg/tools/glustercli-auth-header.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import sys
from argparse import ArgumentParser
from datetime import datetime, timedelta

import jwt


def jwt_token(user, secret):
claims = dict()
claims['iss'] = user
claims['iat'] = datetime.utcnow()
claims['exp'] = datetime.utcnow() + timedelta(seconds=10)

token = jwt.encode(claims, secret, algorithm='HS256')
Copy link
Member

Choose a reason for hiding this comment

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

This looks fine, To provide more security, we need to add URL tampering protection also
@aravindavk any thoughts on this

in the server side, we need to enable (HTTP method) validation to avoid miss use of the token.

Copy link
Member Author

Choose a reason for hiding this comment

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

makes sense. Please open new issue for the same.

return (b'Authorization: bearer ' + token).decode()


if __name__ == "__main__":
parser = ArgumentParser()
parser.add_argument("--user", default="glustercli")
parser.add_argument("--secret-file", required=True)
args = parser.parse_args()

secret = ""
try:
with open(args.secret_file) as f:
secret = f.read()
except IOError as err:
sys.stderr.write("Unable to open secret file\n")
sys.stderr.write("Error: %s\n" % err)
sys.exit(1)

print(jwt_token(args.user, secret))