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

Privileged access subsystem #393

Merged
merged 13 commits into from
Oct 20, 2016
Merged

Conversation

whisperity
Copy link
Contributor

@whisperity whisperity commented Oct 3, 2016

This merge requests contains the privileged access infrastructure — closes #339.

If a user is authenticated (via any means described below), full access to the server is given: they can view results and destroy them. If the user is not authenticated, the entire access is denied – there are no further fine-graining, as per discussed with @dkrupp and @gyorb.

The changes — summed up

  • A new Thrift endpoint at /Authentication was implemented, this handles authentication requests
  • Authentication can happen against a username-password storage, an LDAP server or the local PAM chain.
  • Existing interface was not changed, though an invalid session, on a privileged server, returns a HTTP error code which can make the CodeChecker cmd client fail on an exception — clients are expected to handle authentication before live request.
  • Two new configuration files, config/session_config.json and config/session_client.json. Both are created as working and descriptive templates.
    • The first one is the server's configuration. It is copied to WORKSPACE/session_config.json for every server started and contains the authentication setup.
    • The latter one gets copied for every user into their ~/.codechecker_passwords.json as it contains per-user credentials.
  • Web-browser client authenticates via basic pop-ups (HTTP WWW-Authenticate header)
  • CodeChecker cmd got a new subcommand: login. With this, users can authenticate on a server and kill their session.
    • The ~/.codechecker_passwords.json file can contain preconfigured credentials — if they are valid, executing a live query without a session will automatically log the user in for screen security and convenience.
    • Session information for a user is stored in a temporary file, identified on the system with the running user's name.

Full documentation for public use is available in docs/authentication.md.

@Xazax-hun
Copy link
Contributor

Did you consider storing client side information in the designated codechecker workspace? Does it make sense to have a global one?

@gyorb
Copy link
Contributor

gyorb commented Oct 4, 2016

If a user wants only to check the results and use only the command line no workspace is required. It is possible to store multiple server authentication credentials in one file.
By default a global one is created but the user should be able to use authentication files from different places if needed.

@@ -40,7 +40,7 @@ Tested on Ubuntu LTS 14.04.2

# get ubuntu packages
# clang-3.6 can be replaced by any later versions of clang
sudo apt-get install clang-3.6 doxygen build-essential thrift-compiler python-virtualenv gcc-multilib git wget
sudo apt-get install clang-3.6 doxygen build-essential thrift-compiler python-virtualenv gcc-multilib git wget libldap2-dev libsasl2-dev libssl-dev

Copy link
Contributor

Choose a reason for hiding this comment

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

Is ldap a strong dependency or only an optional one? I think in case this is optional (it should be IMO), lets have a separate list with optional dependencies.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. CQ @gyorb @dkrupp on what the best way to support this through the automatic build chain (maybe a --with-ldap option in build_package.py?) would be.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think installing these extra requirements could go into the separate authentication documentation and start that if the user requires authentication he should install these extra packages and python modules.

ret = run_cmd(auth_cmd, thrift_files_dir, env, silent=silent)
if ret:
LOG.error('Failed to generate authentication interface files')
return ret
Copy link
Contributor

Choose a reason for hiding this comment

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

There seems to be some repetition. Does a local function make this code shorter overall?

Copy link
Contributor

Choose a reason for hiding this comment

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

So far we generated the source files with Thrift files differently for the report server (python only) and the report viewer (python and javascript). The authentication is python only which could be refactored into a separate function with the report server source file generation. I'm not sure it will be so much shorter, right now the generate_thrift_files function is not that long. If you think it is better you can refactor it into a separate function.

# ------------------------------
# ----------- SERVER -----------
class _Session():
'''A session for an authenticated, privileged client connection'''
Copy link
Contributor

Choose a reason for hiding this comment

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

End comments with periods.

persistency hash is intended to be used for the "session recycle"
feature to prevent NAT endpoints from accidentally getting each
other's session.'''
return hashlib.sha256(auth_string + "@" + client_addr + ":" +
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the port included in the addr? Can multiple sessions from the same client interfere?

self.last_access = datetime.now()


class sessionManager:
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this start with a lowercase letter on purpose?

ret = self.__save["credentials"].get(host + ":" + port)
if not ret:
ret = self.__save["credentials"].get(host)
if not ret:
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you do not need all the indentation.

return ret

def saveToken(self, host, port, token, destroy=False):
if not destroy:
Copy link
Contributor

Choose a reason for hiding this comment

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

Reverse the branches and remove the not.


// handles creating a session token for the user
string performLogin(1: string auth_method,
2: string auth_string)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is it good practice to have one auth string instead of separate user/password etc?

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 was done this way to make sure that further extensions in auth methods (such as, e.g. SSH key based one) is most easily done without further breaking the Authentication API.

# ------------------------------------------------------------

def ThriftClientCall(function):
# print type(function)
Copy link
Contributor

Choose a reason for hiding this comment

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

Commented code.

# import only if necessary; some tests may not add this to PYTHONPATH
from codechecker_lib import session_manager
from codeCheckerDBAccess import codeCheckerDBAccess
from codeCheckerDBAccess.constants import MAX_QUERY_SIZE

self.max_query_size = MAX_QUERY_SIZE
transport = THttpClient.THttpClient(host, port, uri)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is the login done through https?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. (Docs will be updated.)

@whisperity
Copy link
Contributor Author

whisperity commented Oct 4, 2016

To-Do:

  • Session tokens in separate files, proper locking
  • Server configuration into workspace
  • Warn that credentials travel without any encryption

whisperity added a commit to whisperity/CodeChecker that referenced this pull request Oct 4, 2016
@@ -0,0 +1,145 @@
CodeChecker authentication subsytem
===================================

Copy link
Contributor

Choose a reason for hiding this comment

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

A new additional package dependencies section could be created here, what packages should be installed by the user.

#
# Create a dummy authentication-enabled configuration and an auth-enabled server
session_cfg_file = os.path.join(pkg_root, "config", "session_config.json")
with open(session_cfg_file, 'r') as scfg:
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we open the file only once? (read up the config modify it and write out)

import json
import hashlib
import time
import ldap
Copy link
Contributor

Choose a reason for hiding this comment

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

importing ldap modules could be done optionally and if importing fails only the ldap authentication method will be disabled.

try:
   import ldap
   import ldap.sasl
except ImportError:
    # no ldap authentication is available

@whisperity whisperity changed the title Privileged access system & LDAP authentication (#339) Privileged access subsystem Oct 5, 2016

The `CodeChecker cmd` client needs to be authenticated for a server before any data communication could take place.

The client's configuration file is expected to be at `~/.codechecker_passwords.json`, which is created at the first command executed
Copy link
Contributor

Choose a reason for hiding this comment

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

Please extend the documentation that the user should modify the access rights to the password file so only he can access it: chmod 0600 ~/.codechecker_password.json

Copy link
Contributor Author

Choose a reason for hiding this comment

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

😏 Better idea is to create the file initially as 0600. Though I added it to the documentation and a warning (in ssh style) for the user if the file is with the wrong permissions.

@gyorb gyorb modified the milestone: release 5.7 Oct 11, 2016
@gyorb
Copy link
Contributor

gyorb commented Oct 14, 2016

Please update your branch, so all the tests can run on Linux and on OSX too. The CI is fixed now. Thanks!

@whisperity
Copy link
Contributor Author

Should I cherry-pick the test fixing changes or should I do a full rebase? With this many commits in the history – and the reviews – I would vote for cherry picking.

@Xazax-hun
Copy link
Contributor

I think rebasing is usually a cleaner solution for the history's sake.

whisperity added a commit to whisperity/CodeChecker that referenced this pull request Oct 16, 2016
@whisperity whisperity force-pushed the authentications branch 2 times, most recently from 66d4102 to a731109 Compare October 18, 2016 14:30
whisperity added a commit to whisperity/CodeChecker that referenced this pull request Oct 18, 2016
Copy link
Contributor

@gyorb gyorb left a comment

Choose a reason for hiding this comment

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

Are there any commits you could squash?

with open(session_cfg_file, 'w') as scfg:
json.dump(self.__save, scfg, indent=2, sort_keys=True)
with open(self.token_file, 'w') as scfg:
fcntl.lockf(scfg, fcntl.LOCK_EX)
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this way of locking unix specific? Will it work on OSX and maybe later on Windows?

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 is. It will work on OS X but will definitely not work on Windows, the filesystem concept of the two system families are entirely different.

Maybe we could use the package portalocker? It is essentially just a branching wrapper, but it looks like it could work good enough.

Copy link
Contributor

Choose a reason for hiding this comment

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

I think it would be nice to use a platform independent solution. portalocker seems to be a nice choice.

@whisperity
Copy link
Contributor Author

As for squashing: not really without messing up order (I have done some reordering in this last rebase.)

I could squash the first two commits as they are one unit, I could also reorder the "update due to reviews" stuff and put it with the "misc." changes as one commit at the end, though. But putting any more commits together would create a big list of unrelated stuff in one commit...

@gyorb
Copy link
Contributor

gyorb commented Oct 19, 2016

Let's squash that few commits you mentioned and keep the rest.

 - Added a new Thrift service for handling authentication requests (not
   yet documented)
 - Added proper HTTP responses for the browser-based auth realm to work
 - Added wrapper for authentication requests on the command-line client
 - Implemented persistency for sessions (server) and valid tokens (client)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LDAP based authentication
3 participants