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: restricted APIs #2400

Merged
merged 1 commit into from
Nov 8, 2021
Merged

feat: restricted APIs #2400

merged 1 commit into from
Nov 8, 2021

Conversation

notanatol
Copy link
Contributor

@notanatol notanatol commented Aug 9, 2021

RBAC for bee API and debug API.

It introduces a new command line argument --restricted that, if provided, would require obtaining a security token (via /auth endpoint) and providing an Authorization header on every HTTP request to either of the APIs.

A /refresh endpoint is being added to allow generation of new token based on the old one, without the need for credentials.

The admin username and password should be provided as configuration parameters:

  • --admin-password
  • --token-encryption-key

The value of the admin-password has to be hashed using apache's bcrypt as described here: https://docs.nginx.com/nginx/admin-guide/security-controls/configuring-http-basic-authentication/

In order to make it work please copy policy.csv and security.conf files (from the project root folder) to your home directory in the location:

  • ~/.config/bee

To get the token:

POST /auth
Authorization: Basic dGVzdDp0ZXN0

{
    "role": "role0",
    "expiry": 30
}

The received token is stateless and can be refreshed:

POST {{api}}/refresh
Authorization: Bearer IamTheOldTokenR2MqGUjukiJ5C56rWql1dNE2yRdg==

{
    "expiry": 15
}

expiry is in seconds from current time.

This change is Reviewable

@notanatol notanatol marked this pull request as draft August 9, 2021 09:21
@notanatol notanatol requested review from acud, istae and janos August 11, 2021 15:06
@notanatol notanatol added the ready for review The PR is ready to be reviewed label Aug 11, 2021
@notanatol notanatol changed the title feat: protected APIs feat: restricted APIs Aug 11, 2021
@notanatol notanatol marked this pull request as ready for review August 12, 2021 08:41
@agazso
Copy link
Member

agazso commented Aug 12, 2021

  • do we leave /chunks/stream, /stewardship/{address} and /consumed/* endpoints unrestricted?

In restricted mode I would not leave any endpoints unrestricted.

@notanatol notanatol added in progress ongoing development , hold out with review and removed ready for review The PR is ready to be reviewed labels Aug 12, 2021
Copy link
Member

@janos janos left a comment

Choose a reason for hiding this comment

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

I would just point out that tests are important for this functionality, even though I know that the status is still in progress.

Copy link
Member

@janos janos left a comment

Choose a reason for hiding this comment

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

Thanks @notanatol for implementing bcrypt. There are a few more comments regarding configuration handling and plain text http responses.

@mrekucci mrekucci requested review from acud and janos August 15, 2021 12:38
Copy link
Contributor

@mrekucci mrekucci left a comment

Choose a reason for hiding this comment

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

Reviewed 2 of 13 files at r1.
Reviewable status: 2 of 14 files reviewed, 14 unresolved discussions (waiting on @acud, @agazso, @istae, @janos, and @notanatol)


pkg/api/api.go, line 280 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

For extensibility, a json object with field "key" would be better.

I agree, we should use jsonhttp.Created for response with a JSON object.


pkg/api/router.go, line 31 at r3 (raw file):

	// handle is a helper closure which simplifies the router setup.
	handle := func(path string, handler http.Handler) {

Since almost every path should be protected I'd recommend implementing the permission check directly into the handle closure and at the same time create new closure (which will be used by the handle closure) for unprotected endpoints.


pkg/auth/auth.go, line 25 at r3 (raw file):

type Authenticator struct {
	user     string

If the user isn't an object it's usually referred to as username and password instead of pass.


pkg/auth/auth.go, line 75 at r3 (raw file):

}

func isValidHash(password string, hash []byte) bool {

Since it is used just in the Authorize I'd inline this there.


pkg/auth/auth.go, line 84 at r3 (raw file):

}

func (a *Authenticator) AddKey(user, role string) string {

The user isn't used.


pkg/auth/auth.go, line 93 at r3 (raw file):

	b := make([]byte, 16)
	_, _ = rand.Read(b)

The error shouldn't be silenced.


pkg/auth/auth.go, line 114 at r3 (raw file):

	sub := ar.role // the user that wants to access a resource.

	if allow, _ := a.enforcer.Enforce(sub, obj, act); !allow {

The !allow variable can be returned directly.


pkg/debugapi/router.go, line 32 at r3 (raw file):

	router := mux.NewRouter()
	router.NotFoundHandler = http.HandlerFunc(jsonhttp.NotFoundHandler)

I'd recommend creating the same closure as in the regular API router and implementing the permission check directly into the handle closure similar as was recommended in the regular router API.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewable status: 0 of 16 files reviewed, 14 unresolved discussions (waiting on @acud, @agazso, @istae, @janos, @mrekucci, and @notanatol)


pkg/api/api.go, line 254 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

This handler is returning responses in plain text, while other handlers are returning json-encoded responses. Is this what it intended?

No, by mistake, now fixed.


pkg/api/api.go, line 280 at r3 (raw file):

Previously, mrekucci wrote…

I agree, we should use jsonhttp.Created for response with a JSON object.

Done.


pkg/api/api.go, line 306 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

This middleware should not return plain text responses as it is part of the json-encoded endpoints. I think that there should be consistency, as clients may assume json encoded response even without checking content-type.

fixed


pkg/api/api.go, line 319 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

Use json encoded response.

fixed


pkg/api/api.go, line 326 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

JSON encoded response should be more aligned with other responses.

fixed


pkg/api/api.go, line 334 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

I think that this response deserves a body, json encoded, explaining that the auth key is not allowed.

fixed


pkg/auth/auth.go, line 54 at r1 (raw file):

Previously, janos (Janoš Guljaš) wrote…

The New constructor should receive variables for user and pass as arguments, instead to put a hard requirement on env variable configuration. The configuration should be handled in the cmd package and passed to other components as variables. With this approach, configuration is leaking into components. Another thing is that variables are names pretty generically. This can cause a miss-configuration, I would suppose to prefix them with BEE_, or just to make them as struct fields in the existing cmd configuration and handle them just as any other variables.

Done.


pkg/auth/auth.go, line 55 at r1 (raw file):

Previously, janos (Janoš Guljaš) wrote…

I would suggest to try to avoid or discourage users to configure plain text passwords. Instead to require to pass the passwords in the form of a hash, which they can generate either by some tool (online or offline) or with a dedicated bee command. This is the same as apache or nginx are requiring to have auth passwords configured as hashed with htpasswd or any other tool that is able to hash with supported hashers. I am suggesting golang.org/x/crypto/bcrypt.

fixed


pkg/auth/auth.go, line 64 at r1 (raw file):

Previously, janos (Janoš Guljaš) wrote…

I am suggesting to have the password hashed and checked with, for example, bcrypt.GenerateFromPassword.

fixed


pkg/auth/auth.go, line 25 at r3 (raw file):

Previously, mrekucci wrote…

If the user isn't an object it's usually referred to as username and password instead of pass.

fixed


pkg/auth/auth.go, line 75 at r3 (raw file):

Previously, mrekucci wrote…

Since it is used just in the Authorize I'd inline this there.

fixed


pkg/auth/auth.go, line 84 at r3 (raw file):

Previously, mrekucci wrote…

The user isn't used.

fixed


pkg/auth/auth.go, line 93 at r3 (raw file):

Previously, mrekucci wrote…

The error shouldn't be silenced.

fixed


pkg/auth/auth.go, line 114 at r3 (raw file):

Previously, mrekucci wrote…

The !allow variable can be returned directly.

fixed


pkg/debugapi/router.go, line 306 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

The same comments on the api package applies here.

Done.

Copy link
Member

@janos janos left a comment

Choose a reason for hiding this comment

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

Thanks @notanatol for addressing my comments. I have a few more, which are mostly opinions that are also part of the existing codebase.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewed 1 of 8 files at r5.
Reviewable status: 0 of 19 files reviewed, 18 unresolved discussions (waiting on @acud, @istae, @janos, @mrekucci, and @notanatol)


pkg/api/api.go, line 261 at r5 (raw file):

Previously, janos (Janoš Guljaš) wrote…

These types does not need to be exported, and can be provided through export_test.go for tests.

fixed


pkg/api/api.go, line 288 at r5 (raw file):

Previously, janos (Janoš Guljaš) wrote…

Usually errors are logged as well.

fixed


pkg/api/api.go, line 345 at r5 (raw file):

Previously, janos (Janoš Guljaš) wrote…

This error can be logged also.

Done.


pkg/api/router.go, line 31 at r3 (raw file):

Previously, mrekucci wrote…

Since almost every path should be protected I'd recommend implementing the permission check directly into the handle closure and at the same time create new closure (which will be used by the handle closure) for unprotected endpoints.

added


pkg/debugapi/router.go, line 334 at r5 (raw file):

Previously, janos (Janoš Guljaš) wrote…

This error can also be logged.

fixed


pkg/auth/mock.go, line 1 at r5 (raw file):

Previously, janos (Janoš Guljaš) wrote…

Usually, mocks are in a separate package beside the main implementation. Mainly to keep the package api clean from testing code.

Done.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewed 3 of 13 files at r1, 1 of 4 files at r2, 3 of 7 files at r4, 2 of 8 files at r5, 9 of 10 files at r6, 1 of 7 files at r7.
Reviewable status: 13 of 20 files reviewed, 18 unresolved discussions (waiting on @acud, @istae, @janos, and @notanatol)


pkg/node/devnode.go, line 119 at r3 (raw file):

Previously, janos (Janoš Guljaš) wrote…

The node package does not handle env variables, it explicitly expect input arguments to functions. Currently all configuration is done in the cmd/bee package and I would suggest to make the same with these parameters also. Just adding string flags as with any other configuration parameters will also provide the possibility to configure them with env variables, the same way as with any other parameter.

Done.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

should be addressed now, thanks.

Reviewable status: 13 of 20 files reviewed, 18 unresolved discussions (waiting on @acud, @istae, @janos, and @notanatol)

pkg/auth/auth.go Outdated
return nil, err
}

passwordHash, err := bcrypt.GenerateFromPassword([]byte(password), 14)
Copy link
Member

Choose a reason for hiding this comment

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

I had in mind to avoid keeping in configuration password in plain text by using bcrypt, but this is not doing much for that as it is still required to set password in plain text in config in order to hash it here. The hash should be set in the configuration, instead the plain password. The hash may be generated by a separate command or a tool like htpasswd or any online bcrypt site. I think that the cli command would be nice. It does add a ux complexity, but I think for a justified security reason.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

should we maybe add it as a separate command?
/cmd/bee <- current entry point
/cmd/passwordgen <- new command that takes a plain text and returns its hash

or maybe just document so the user could use existing tools?

Copy link
Member

Choose a reason for hiding this comment

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

My opinion is that a separate command would provide a direct way to obtain the hash, and to have in documentation how a password can be generated. But for this PR, the command is not necessary, only documenting where the hash can be generated. For example, nginx relied on apache's htpasswd tool for many years and clearly documented how to use it in relation to nginx. So I would not rush with the command, but I think that it provides a nice convenience.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewed 1 of 10 files at r6, 6 of 7 files at r7, all commit messages.
Reviewable status: all files reviewed, 22 unresolved discussions (waiting on @istae, @janos, and @notanatol)


pkg/api/api.go, line 267 at r7 (raw file):

Previously, janos (Janoš Guljaš) wrote…

These log messages would be better with more information. Like with a prefix indicating that it is from the api and which handler.

Done.


pkg/auth/mock/auth.go, line 1 at r7 (raw file):

Previously, janos (Janoš Guljaš) wrote…

Add copyright header.

Done.

@notanatol notanatol added ready for review The PR is ready to be reviewed and removed in progress ongoing development , hold out with review labels Aug 18, 2021
Copy link
Contributor

@mrekucci mrekucci left a comment

Choose a reason for hiding this comment

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

Reviewed 1 of 7 files at r4, 1 of 8 files at r5, 5 of 10 files at r6, 2 of 7 files at r7, 4 of 8 files at r8, 2 of 2 files at r9, 6 of 6 files at r10, all commit messages.
Reviewable status: 21 of 22 files reviewed, 22 unresolved discussions (waiting on @acud, @istae, @janos, and @notanatol)


.github/workflows/beekeeper.yml, line 50 at r10 (raw file):

        run: |
          printf ${{ secrets.CR_PAT }} | docker login ghcr.io -u bee-worker --password-stdin
          make beekeeper BEEKEEPER_INSTALL_DIR=/usr/local/bin BEEKEEPER_USE_SUDO=true BEEKEEPER_BRANCH=auth-health-check 

Remove the two trailing spaces at the end.


pkg/api/router.go, line 47 at r1 (raw file):

Previously, acud (acud) wrote…

this is a bit tricky, since there is no check about the identity of the caller here, any malicious dapp can try to query the API, then figure it is restricted, and then just add itself using this API endpoint as a legitimate API user

Agree, this should be protected by username/password.


pkg/auth/auth.go, line 36 at r10 (raw file):

	[request_definition]
	r = sub, obj, act
	

Remove trailing spaces, also below.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewable status: 19 of 22 files reviewed, 22 unresolved discussions (waiting on @acud, @istae, @janos, @mrekucci, and @notanatol)


pkg/api/router.go, line 47 at r1 (raw file):

Previously, mrekucci wrote…

Agree, this should be protected by username/password.

Done.


pkg/auth/auth.go, line 59 at r7 (raw file):

Previously, janos (Janoš Guljaš) wrote…

My opinion is that a separate command would provide a direct way to obtain the hash, and to have in documentation how a password can be generated. But for this PR, the command is not necessary, only documenting where the hash can be generated. For example, nginx relied on apache's htpasswd tool for many years and clearly documented how to use it in relation to nginx. So I would not rush with the command, but I think that it provides a nice convenience.

I'll make sure to document how to hash the password.

Copy link
Contributor

@mrekucci mrekucci left a comment

Choose a reason for hiding this comment

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

:lgtm:

Reviewed 3 of 3 files at r11, all commit messages.
Reviewable status: all files reviewed, 22 unresolved discussions (waiting on @acud, @istae, and @janos)

Copy link
Member

@janos janos left a comment

Choose a reason for hiding this comment

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

All looks good. I just have a question about the removed checks in ci beekeeper configuration.

- name: Test content availability
id: content-availability
run: beekeeper check --cluster-name local-dns --checks=ci-content-availability
- name: Test authenticated health
Copy link
Member

Choose a reason for hiding this comment

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

Should we wait for new beekeeper version to be released with the ci-auth check and to get back ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I will mark it as draft and merge it only after BK changes are in.

Copy link
Member

@acud acud left a comment

Choose a reason for hiding this comment

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

bravisimo... looks very nice 👏 I have a few very small suggestions

Reviewed 1 of 7 files at r4, 5 of 10 files at r6, 1 of 7 files at r7, 3 of 8 files at r8, 2 of 2 files at r9, 3 of 6 files at r10, 1 of 3 files at r11, 5 of 6 files at r12, 1 of 1 files at r13, all commit messages.
Reviewable status: all files reviewed, 22 unresolved discussions (waiting on @istae and @janos)


pkg/api/api.go, line 267 at r7 (raw file):

Previously, notanatol (Anatol) wrote…

Done.

same here and in the rest of the PR :)


pkg/api/api.go, line 267 at r13 (raw file):

	if !ok {
		s.logger.Debug("api: auth handler: missing basic auth")

if the Debug does not provide any additional information over Error, it can be safely omitted

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewed 3 of 8 files at r8, 1 of 2 files at r9, 3 of 6 files at r10, 1 of 3 files at r11, 5 of 6 files at r12, 1 of 1 files at r13.
Reviewable status: 19 of 22 files reviewed, 22 unresolved discussions (waiting on @acud, @istae, @janos, @mrekucci, and @notanatol)


pkg/api/api.go, line 267 at r7 (raw file):

Previously, acud (acud) wrote…

same here and in the rest of the PR :)

Done.

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewed 1 of 1 files at r16, 5 of 9 files at r18, 16 of 16 files at r19, all commit messages.
Reviewable status: all files reviewed, 22 unresolved discussions (waiting on @istae and @janos)

@codecov-commenter
Copy link

codecov-commenter commented Oct 25, 2021

Codecov Report

Merging #2400 (1a77873) into master (175dfb4) will decrease coverage by 0.17%.
The diff coverage is 56.21%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #2400      +/-   ##
==========================================
- Coverage   63.38%   63.21%   -0.18%     
==========================================
  Files         232      234       +2     
  Lines       25901    26264     +363     
==========================================
+ Hits        16418    16602     +184     
- Misses       7984     8143     +159     
- Partials     1499     1519      +20     
Impacted Files Coverage Δ
cmd/bee/cmd/start.go 2.35% <0.00%> (-0.03%) ⬇️
pkg/auth/handler.go 0.00% <0.00%> (ø)
pkg/api/api.go 65.57% <41.17%> (-9.54%) ⬇️
cmd/bee/cmd/start_dev.go 16.80% <50.00%> (+1.67%) ⬆️
pkg/auth/auth.go 59.02% <59.02%> (ø)
pkg/debugapi/router.go 97.27% <87.50%> (-2.73%) ⬇️
cmd/bee/cmd/cmd.go 66.46% <100.00%> (+0.62%) ⬆️
pkg/api/router.go 98.67% <100.00%> (+0.11%) ⬆️
pkg/debugapi/debugapi.go 100.00% <100.00%> (ø)
pkg/p2p/libp2p/stream.go 70.58% <0.00%> (-11.77%) ⬇️
... and 9 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 175dfb4...1a77873. Read the comment docs.

@@ -14,7 +14,7 @@ jobs:
REPLICA: 3
RUN_TYPE: "PR RUN"
SETUP_CONTRACT_IMAGE_TAG: "0.2.0"
BEEKEEPER_BRANCH: "master"
BEEKEEPER_BRANCH: "restricted-2"
Copy link
Member

Choose a reason for hiding this comment

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

just a reminder to revert this before merging

@@ -62,7 +62,7 @@ jobs:
- name: Set local cluster
run: |
make beelocal ACTION=add-hosts
timeout 10m make deploylocal BEEKEEPER_CLUSTER=local-dns
timeout 10m make deploylocal BEEKEEPER_CLUSTER=local-dns BEEKEEPER_BRANCH=restricted-2
Copy link
Member

Choose a reason for hiding this comment

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

and these too...

Copy link
Contributor Author

@notanatol notanatol left a comment

Choose a reason for hiding this comment

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

Reviewed 3 of 8 files at r20, 1 of 2 files at r21, 2 of 2 files at r22, 2 of 2 files at r25, 2 of 3 files at r26, 2 of 2 files at r29, 1 of 1 files at r30, all commit messages.
Reviewable status: all files reviewed, 24 unresolved discussions (waiting on @istae, @janos, and @notanatol)

Copy link
Member

@janos janos left a comment

Choose a reason for hiding this comment

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

Nice 👍. I have only one minor comment.

func (a *Authenticator) Enforce(apiKey, obj, act string) (bool, error) {
decoded, err := base64.StdEncoding.DecodeString(apiKey)
if err != nil {
a.log.Error("decode token", err)
Copy link
Member

Choose a reason for hiding this comment

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

I would avoid both logging and returning errors in this function.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm going to reduce logging in a future PR but right now we need detailed information when certain cases occur, like token expiration.

@acud acud added this to the v1.4.0 milestone Nov 2, 2021
@sonarqubecloud
Copy link

sonarqubecloud bot commented Nov 8, 2021

Kudos, SonarCloud Quality Gate passed!    Quality Gate passed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 4 Code Smells

No Coverage information No Coverage information
1.1% 1.1% Duplication

@notanatol notanatol merged commit c752928 into master Nov 8, 2021
@notanatol notanatol deleted the permissions branch November 8, 2021 13:00
@acud acud modified the milestones: v1.4.0, v1.4.1 Nov 23, 2021
@notanatol notanatol linked an issue Nov 30, 2021 that may be closed by this pull request
@notanatol notanatol mentioned this pull request Feb 3, 2022
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pull-request ready for review The PR is ready to be reviewed
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Unauthorized localhost access
6 participants