diff --git a/README.md b/README.md new file mode 100644 index 0000000..903e395 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Potato Farmer + +Manages `potato_field` and `potato_plants`. Main way to administrate containers. + +## Setup + +### Dependencies + +There are several dependencies required for the development and deployment of this project. +The first is Docker Engine and Docker Compose. The second is Python3. +The following list links to the installation instructions for each of these: + +* [Docker Engine installation instructions.](https://docs.docker.com/engine/install/) +* [Docker Compose installation instructions.](https://docs.docker.com/compose/install/) +* [Python3 installation instructions.](https://www.python.org/downloads/) + +### Sub-repository management + +The tool `setup.py` can be used to clone (or symlink) project repositories for deployment. + +For instance, the following command will clone the `potato_plant_dashboard` and `potato_plant_missions` +repositories (hosted in the BourbonWarfare organization) into the project root: +```bash +python3 setup.py add plant_dashboard plant_missions +``` +The resulting repositories would then be cloned into `potato_field/` and are symlinked into the +`potato/` directory. + + diff --git a/docs/backend/diagrams/backend.drawio b/docs/backend/diagrams/backend.drawio new file mode 100644 index 0000000..edd4003 --- /dev/null +++ b/docs/backend/diagrams/backend.drawio @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/docs/backend/diagrams/backend.html b/docs/backend/diagrams/backend.html new file mode 100644 index 0000000..1bd5ed7 --- /dev/null +++ b/docs/backend/diagrams/backend.html @@ -0,0 +1,11 @@ + + + + +backend-architecture-diagram + + +
+ + + \ No newline at end of file diff --git a/docs/backend/diagrams/backend.png b/docs/backend/diagrams/backend.png new file mode 100644 index 0000000..6b3687a Binary files /dev/null and b/docs/backend/diagrams/backend.png differ diff --git a/docs/threat-model.md b/docs/threat-model.md new file mode 100644 index 0000000..8794002 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,154 @@ +# `potato_farmer` Threat Model + +## Project Description + +This project manages `potato_field` and `potato_plants`. It's the main way to administrate +containers; services that run in the backend to support the main application. + +### User System + +There will be a user system with a hierarchy of users, ranging from administrators to normal users +that have restricted access to applications. A user should be uniquely identified and should not +be able to pose as another user. + +### Backend Services + +There will be a number of backend services that expose an API for other backend applications to +interact with. These backend services should **not** be exposed to the outside world; rather, +this should be managed by the API Gateway. + +### API Gateway + +The API Gateway takes requests from the outside world -- that is, from users and outside automated +services -- and collates data and operations from one or more backend services to satisfy the +request. The API Gateway should generally be the **only** way for users to access applications. + +## Threats + +### (1) User Impersonation + +As users should be uniquely identified, it would violate the assumptions of our application if a +user was able to successfully pose as another user. This would possibly permit a user to access +another user's information, ruin their reputation in community spaces/applications, or utilize +privileges that they should not have access to, as described in (2). + +### (2) Privilege Escalation + +As there will be a hierarchy of users with varying capabilities, it should not be possible for a +user to gain additional privileges without them being granted by an administrator. Violation of +this assumption would allow ordinary users to take advantage of privileges they should not have +access to, potentially wreaking havoc regardless of malicious intent. + +### (3) Direct Access to Backend Services + +Backend services should not be directly accessible. Violation of this principle could permit actors +to access APIs they should not have access to, potentially wreaking havoc on internal state or +exposing data that should remain private to them. + +### (4) API Gateway Overloading + +As the API Gateway is the main way for users to interact with applications, it serves as a single +point of failure for the entire application. By overloading the API Gateway, malicious actors (or +just a sudden spike of traffic) can disrupt the availability of the application for all users. + +### (5) Network Eavesdropping + +There may be actors that are listening in on communications between our users or between services +in our application. Through eavesdropping on network communications, malicious actors could learn +private user/system/operational data that they should not have access to. Further, they may be able +to construct spoofed requests for abuse in conjunction with Threat (3). + +## Defense Mechanisms + +### (1, 2) User Authentication + +To protect against user impersonation, the application will utilize mechanisms of user authentication +to validate the user's identity prior to allowing access to any privileged operations or data (including +operations or data tied to user identity). + +#### Password Authentication + +Upon user creation, users will select a password to use for authentication. If possible, we should +include a password strength meter to help users choose a strong password. We will impose several +requirements to ensure the password is not trivially bruteforceable. + +1. Minimum of 12 characters; maximum of 60 characters +2. Includes at least one number +3. Includes at least one uppercase and lowercase alphabetic character +4. Includes at least one non-alphabetic character (symbol) + +These passwords will be stored in a User Authentication service. The modern algorithm Argon2id is +considering the most secure and correct to use in modern applications, but is resource-heavy. All +passwords should be salted, but Argon2id should handle this itself. Password hashes should be +compared using only safe functions that have been vetted by security experts. + +#### Two Factor Authentication (2FA) + +As user passwords may be shared between multiple services, may be bruteforceable, may be leaked +through a variety of means, etc., our application will utilize 2FA to further validate user +identities. This 2FA mechanism will be opt-in and will utilize email to send a user a six-digit +one-time code to be validated after username/password validation succeeds. Bypassing this mechanism +requires malicious actors to also have access to the user's email. + +### (1, 2) User Sessions + +It is not practical to require users to authenticate for every request. This presents a poor UX +and is expensive for our backend services. To alleviate this pressure, we will implement user +sessions to permit users to authenticate once and continue submitting requests for a time without +requiring authentication again. + +User sessions will be implemented by a `SessionID` that is a 128-bit string, the length chosen +to harden `SessionID`s against bruteforce attacks. These will be randomly generated with +cryptographically strong randomness. `SessionID`s will be stored in a Redis memory store alongside +a `UserID` and a session timeout. Sessions will have an idle timeout of 1 hour and an absolute +timeout of 1 day after authentication. We could consider introducing a renewal timeout as well, +but this would add additional complexity. + +Users will receive their 128-bit `SessionID` after successful user authentication. This will have +to be sent with every privileged request in a manner specified by the application API. The API +Gateway would check the Redis memory store to validate the `SessionID` before serving the request. +Failure to validate the `SessionID` would result in a failure which would be communicated to the user. + +Note that as `SessionID`s will be sent as a HTTP Cookie, it is important to properly protect against +CSRF attacks with the proper use of CSRF tokens. + +### (3) Internal Docker Network + +Backend services will operate within an internal Docker network that would not have exposed ports +to the outside world. Only the API Gateway will be publicly accessible. This should prevent +outside access of restricted backend services and force users to use the API Gateway to interact +with applications. + +To clarify, the API Gateway will also be containerized and will access backend service APIs through +the internal Docker network. Docker containers within the internal network will be assumed to be +trustworthy and can talk at will. + +### (4) Rate Limiting + +As the application continues to grow in scale, users will be rate limited and the API Gateway will +not serve requests beyond the rate limit. Rate limiting should be stricter for non-authenticated +endpoints. Users who repeatedly attempt to violate rate limiting rules should be restricted from +accessing authenticated endpoints. Anonymous actors who repeatedly attempt to violate rate limiting +rules may have their source IP banned (note that this may be problematic if many machines share a +NAT). + +We may consider implementing our own rate limiting mechanisms, or using off-the-shelf solutions. +This mechanism will also likely depend on the implementation of our API Gateway. + +### (5) TLS/SSL/DTLS + +All user-application communications and backend-service communications will utilize TLS/SSL for TCP +connections to ensure that private user/system/operational data is not leaked to external eavesdroppers. +Thanks to Mechanism (3), it should not be possible for eavesdroppers to have access to private +backend service communications, but these should still be encrypted as much as possible regardless +to ensure a layered defense. + +For UDP communications, we can consider a variety of integrity mechanisms. DTLS has been advertised +as an integrity mechanism analogous to TLS for TCP, but the practical usage of this protocol may +need more investigation. + +## Appendix + +Consult the [Web Security Confluence Page](https://bourbonwarfare.atlassian.net/wiki/spaces/~62fd97fc88b05653fa7d6975/pages/557057/Web+Security) +for a list of some of the sources consulted in the making of this document, as well as a list of +resources on how to implement some of these security mechanisms correctly. diff --git a/setup.py b/setup.py index 44d964d..8b03035 100644 --- a/setup.py +++ b/setup.py @@ -15,31 +15,44 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . """ + + import argparse -import shutil import os +import shutil import subprocess +import sys + BW_GITHUB_PATH = 'git@github.com:BourbonWarfare/potato_{}.git' THIS_PATH = os.path.dirname(os.path.realpath(__file__)) REPO_SYMLINK_PATH_UNFORMATTED = 'potato/{}' -def has_requirements(): - if shutil.which('git') is None: - return False - return True +def missing_requirements(): + '''Returns a list of missing requirements; the empty list is returned + if all requirements are satisfied.''' + + reqs = ['git'] + + res = [] + for req in reqs: + if shutil.which(req) is None: + res.append(req) + return res + def is_path(potential_path): if os.path.isdir(potential_path): return potential_path - else: - raise NotADirectoryError(potential_path) + + raise NotADirectoryError(potential_path) + def link_directories(repo_path): try: is_path(repo_path) - except: + except NotADirectoryError: print('"{}" is not a valid path to symlink!'.format(repo_path)) return @@ -51,46 +64,81 @@ def link_directories(repo_path): print('Linking "{}" to "{}"'.format(repo_path, symlink_path)) os.symlink(repo_path, symlink_path) + def clone_repo(clone_path, repository): repo_name = "potato_" + repository repo_path = os.path.join(clone_path, repo_name) - subprocess.run(['git', 'clone', BW_GITHUB_PATH.format(repository), repo_path]) - link_directories(repo_path) + try: + subprocess.run(['git', 'clone', BW_GITHUB_PATH.format(repository), repo_path], + check=False) + link_directories(repo_path) + except Exception as e: + print("Error cloning {} to {}: {}!".format( + BW_GITHUB_PATH.format(repository), repo_path, e)) + def create_repo(clone_path, repository): print("Not implemented") + def link(args): link_directories(args.repositories[0]) - + + def add(args): args.path = os.path.join(args.path, 'potato_field') for repo in args.repositories: - clone_repo(args.path, repo) - -if not has_requirements(): - exit() - -parser = argparse.ArgumentParser( - prog = 'setup.py', - description = 'Manages the setup of any repository that falls under potato_field', - epilog = """ - potato_field_manager Copyright (C) 2022 Bailey Danyluk - This program comes with ABSOLUTELY NO WARRANTY. - This is free software, and you are welcome to redistribute it - under certain conditions.""" -) - -parser.add_argument('mode', choices=['link', 'add'], default='add', help='Whether to setup a new repository or link an existing one') -parser.add_argument('repositories', nargs='+', help='Select which repositories to clone and setup') -parser.add_argument('--path', '-p', required=False, type=is_path, default=os.path.dirname(os.path.realpath(__file__)), help='The directory where the repository will be cloned to. If not specified, repository will be cloned within the directory this tool is run') -args = parser.parse_args() - -if args.mode == 'link': - link(args) -elif args.mode == 'add': - add(args) - - + if args.new: + create_repo(args.path, repo) + else: + clone_repo(args.path, repo) + + +def main(): + missing = missing_requirements() + if len(missing) > 0: + print("The following requirements are missing:", missing) + print("Exiting...") + sys.exit(1) + + parser = argparse.ArgumentParser( + prog='setup.py', + description='Manages the setup of any repository that falls under potato_field', + epilog=""" + potato_field_manager Copyright (C) 2022 Bailey Danyluk + This program comes with ABSOLUTELY NO WARRANTY. + This is free software, and you are welcome to redistribute it + under certain conditions.""" + ) + + parser.add_argument( + 'mode', + choices=['link', 'add'], + default='add', + help='Whether to setup a new repository or link an existing one') + parser.add_argument('repositories', + nargs='+', + help='Select which repositories to clone and setup') + parser.add_argument('--path', '-p', + nargs=1, + required=False, + type=is_path, + default=os.path.dirname(os.path.realpath(__file__)), + help='The directory where the repository will be cloned to. If not \ + specified, repository will be cloned within the directory this tool is run') + parser.add_argument('--new', + required=False, + default=False, + help='Create the repository instead of cloning an existing repository') + args = parser.parse_args() + + if args.mode == 'link': + link(args) + elif args.mode == 'add': + add(args) + + +if __name__ == '__main__': + main()