diff --git a/.gitignore b/.gitignore index e7dff43ac58af825e6105802266a6918f9c56e0c..bd183f3fccddac335407db45f996930f71e5558d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ README.html id_* *.pem -*.key \ No newline at end of file +*.key +*.log diff --git a/Application/backend/main.py b/Application/backend/main.py index 544c803dfc84c5366e46be6e564c4d32375ae6df..4b6bff85441a7eb8eab1b1ec07a9b1b6e0a5b6ab 100644 --- a/Application/backend/main.py +++ b/Application/backend/main.py @@ -33,13 +33,13 @@ def handle_aws_errors(f): app = Flask(__name__) CORS(app) -# Initialize S3 client -# TODO: Add your S3 credentials in .env file +# Initialize S3 client TODO: Add your S3 credentials as ENV variables or in +# file '~/.aws/credentials' s3_client = boto3.client( 's3', - aws_access_key_id=os.getenv('SWTICH_ACCESS_KEY_ID'), + aws_access_key_id=os.getenv('SWITCH_ACCESS_KEY_ID'), aws_secret_access_key=os.getenv('SWITCH_SECRET_ACCESS_KEY'), - endpoint_url=os.getenv('SWTICH_ENDPOINT_URL') + endpoint_url=os.getenv('SWITCH_ENDPOINT_URL') ) # Constants @@ -61,11 +61,11 @@ def enroll(): data = request.get_json() email = data.get('email') password = data.get('password') - + if not email or not password: logger.warning(f"Enrollment attempt with missing credentials: {data}") return jsonify({'status': 'KO:Missing credentials'}), 400 - + # TODO: Implement enrollment logic # 1. Check if user already exists # 2. Create new enrollment with timestamp @@ -74,7 +74,7 @@ def enroll(): 'password': password, # TODO: Hash this password in production 'enrolled_at': datetime.utcnow().isoformat() # Added timestamp } - + return jsonify({'status': 'OK'}), 200 @app.route('/login', methods=['POST']) @@ -87,10 +87,10 @@ def login(): data = request.get_json() email = data.get('email') password = data.get('password') - + if not email: return jsonify({'status': 'KO:Missing email'}), 400 - + # TODO: Implement login logic # 1. Check if user exists # 2. Verify password @@ -100,7 +100,7 @@ def login(): 'active': True, 'login_at': datetime.utcnow().isoformat() # Added timestamp } - + return jsonify({'status': 'OK'}), 200 # TODO: Implement these endpoints @@ -128,4 +128,3 @@ if __name__ == '__main__': app.run(host='0.0.0.0', port=8000) except Exception as e: logger.critical(f"Failed to start application: {str(e)}") - diff --git a/Application/run.sh b/Application/run.sh new file mode 100755 index 0000000000000000000000000000000000000000..29e8cdbb42c725e86e9bbae01c358e1d8d997304 --- /dev/null +++ b/Application/run.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env sh + +[ "$WEB_SSO_DEBUG" ] && set -x + +web_sso_root=${WEB_SSO_ROOT:-'.'} + +venv_act_file="${web_sso_root}/../bin/activate" + +# make sure to have a slash else the 'source' command could fail +s3_cred_env_file="${web_sso_root}/s3_credentials.env" + +[ -r $s3_cred_env_file ] || { + echo >&2 "[error] $s3_cred_env_file: missing credentials ENV file" + exit 1 +} + +source $s3_cred_env_file || exit 1 + +# these are not really used here, just to trigger the shell's :?-test +switch_endpoint_url=${SWITCH_ENDPOINT_URL:?'Please set ENV variable'} +switch_access_key_id=${SWITCH_ACCESS_KEY_ID:?'Please set ENV variable'} +switch_secret_access_key=${SWITCH_SECRET_ACCESS_KEY:?'Please set ENV variable'} +s3_bucket_name=${S3_BUCKET_NAME:?'Please set ENV variable'} + +backend_pid= +backend_log="${web_sso_root}/backend/main.log" +backend_main="${web_sso_root}/backend/main.py" +frontend_main="${web_sso_root}/frontend/main.py" + +function cleanup { + trap EXIT SIGINT SIGTERM + + # @posix + ps -q $backend_pid >/dev/null && kill -TERM $backend_pid + + exit $? +} + +# EXIT raised on Ctrl+D in an interactive shell +trap cleanup EXIT SIGINT SIGTERM + +# try to activate the venv upstream +[ -r $venv_act_file ] && { + source $venv_act_file || + echo >&2 "[warn] $venv_act_file: can't activated the virtual env. Going on anyway..." +} + +# start the backend +python $backend_main > $backend_log & +sleep 2; +backend_pid=$! +ps -q $backend_pid >/dev/null || { + echo >&2 "[error] Backend crashed :-(. See log '${backend_log}'" + exit 1 +} +echo >&2 -e "[ok] Backend started. Logging to file '${backend_log}'" + +# start the frontend -- must be explicitly terminated with Ctrl+C +python $frontend_main diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..ecb6c8fa6924f017d4ccc28dd0dadb8de987bb7b --- /dev/null +++ b/Makefile @@ -0,0 +1,129 @@ +.ONESHELL: + +prefix := $(HOME) +sandbox_root := sandbox/project-web-sso +sandbox_dir := $(prefix)/sandbox/project-web-sso +app_sdir := Application +application_dir := $(sandbox_dir)/$(app_sdir) +s3_cred_file := $(application_dir)/s3_credentials.env +pip_opts := "-r" +frontend_port := 3000 +backend_port := 8000 +portal_url := "http://localhost:$(frontend_port)" +terminal := xfce4-terminal +term_opts := +term_title := "Web SSO application" + +ifeq ($(terminal), xterm) + term_opts += -T $(term_title) -hold +else + # should catch most other emulators out there + term_opts += -T $(term_title) -H +endif + +define _help_msg := +Usage: + + make [command] + +Commands + + help ...guess what ^^ + (v)install install the application locally (in a Python virtual env that will be created) + (t)run (open a terminal and) run the application + +Options + + sandbox_dir where to install the app (under '$(prefix)/'). Default: $(sandbox_dir) + terminal the terminal emulator to be lunched with command 'trun'. Default: $(terminal). Supported: xterm, xfce4-terminal. + +Examples + + Install in default sandbox -- you might need to manually install extra Python + dependencies: + + make install + + Install in a virtualenv sandbox in '/foo/bar': + + make sandbox_dir=/foo/bar vinstall + + Run the application in the current console: + + make run + + Run the application in terminal emulator 'xterm': + + make terminal=xterm trun +endef + +define _s3_creds := +SWITCH_ENDPOINT_URL='<your-s3-endpoint>' +SWITCH_ACCESS_KEY_ID='<your-s3-access-key-id>' +SWITCH_SECRET_ACCESS_KEY='<your-s3-secret-key>' +S3_BUCKET_NAME='project-web-sso' + +export SWITCH_ENDPOINT_URL SWITCH_ENDPOINT_URL SWITCH_ACCESS_KEY_ID SWITCH_SECRET_ACCESS_KEY S3_BUCKET_NAME +endef + +define echoo + printf "%b\n" "$*" +endef + +define echoe + $(echoo) >&2 +endef + +.PHONY: help _sandbox _venv _s3cred install vinstall trun run + +all: help + +_sandbox: + mkdir -p $(sandbox_dir) + +_venv: _sandbox + python -m venv $(sandbox_dir) || exit 1 + +_install: _sandbox + rsync -Cabhv -L --suffix=.bak $(app_sdir) $(sandbox_dir) || exit 1 + pip install $(pip_opts) $(application_dir)/frontend/requirements.txt || + exit 1 + pip install $(pip_opts) $(application_dir)/backend/requirements.txt || + exit 1 + $(MAKE) _s3cred + +_s3cred: + [ -r $(s3_cred_file) ] && { + $(echoe) "[info] $(s3_cred_file): file exists. Won't touch it..." + exit 0 + } + $(echoo) "$(_s3_creds)" > $(s3_cred_file) || exit 1 + chmod 0600 $(s3_cred_file) + $(echoe) "[info] Please adapt your AWS/S3 credentials in file '$(s3_cred_file)'" + +install: + $(MAKE) pip_opts='$(pip_opts) --user' _install || exit 1 + +vinstall: _venv + source $(sandbox_dir)/bin/activate + $(MAKE) _install + $(echoe) "[ok] Application installed in '$(application_dir)'" + $(echoe) "[info] To activate your virtual env, please, open a terminal and run:\n\ + cd $(sandbox_dir) && source bin/activate" + +run: + export WEB_SSO_ROOT=$(application_dir) + $(application_dir)/run.sh + +trun: + which $(terminal) 2> /dev/null || { + $(echoe) "[error] $(terminal): not found. Please set your terminal program and retry:\n\tmake terminal=YOUR_TERMINAL_PROGRAM run" + exit 1 + } + $(terminal) $(term_opts) \ + -e "sh -c 'export WEB_SSO_ROOT=$(application_dir); \ + $(application_dir)/run.sh'" & + + +help: + @: $(info $(_help_msg)) diff --git a/README.md b/README.md index 439c7ab2f43a0377608132077350d4fec78da41d..89cbb364eab8173a4da7412d05751e0bae9aaff5 100644 --- a/README.md +++ b/README.md @@ -320,24 +320,40 @@ The whole stack shall be redeployed whenever any of its images are updated. :construction: **To be finalized** -:warning: Please respect the file layout provided by this repository! +:bulb: Please, respect the file layout provided by this repository. Here is +map: + +``` +├── Ansible +│ ├── keys <= SSH keys with name "id_..." (won't be committed) +│ └── playbooks <= recipes to be extended +│ └── files <= KinD/K8s files +├── Application +│ ├── backend <= main.tf to be extended +│ └── frontend +│ └── views +├── Docker +└── Terraform <= recipes to be extended + └── conf <= Cloud-init files +``` You shall: 1. Fork this repository. -2. Complete the Python back-end file(s) in folder - `Application/backend/main.py`. +2. Complete the Python back-end file `Application/backend/main.py`. See the + section [Development](#development) below. 3. Rebuild the application back-end Docker image, and push it to your public - Docker Hub repositry -- **(:question: TO-DO - We should provide + Docker Hub repository -- **(:question: TO-DO - We should provide instructions)**. This task shall be automated via Ansible -- see below. 4. Complete your Terraform files from the version you developed in [Lab-Terraform](https://gitedu.hesge.ch/lsds/teaching/bachelor/cloud-and-deployment/lab-terraform/-/blob/main/SwitchEngines/README.md) up to Task #8. Your recipe shall handle only the provisioning of the VM plus an S3 storage bucket -- no KinD/Kubectl package installation. Commit - your recipe files (included Cloud-init) and in directory `Terraform/`. + your recipe files and in directory `Terraform/` -- Cloud-init files are + already in sub-folder `conf/`. 5. Complete your Ansible playbook, starting from the version you developed in [Lab-Ansible](https://gitedu.hesge.ch/lsds/teaching/bachelor/cloud-and-deployment/lab-ansible) - Task #10, to (commit all realted files in directory `Ansible/`): + Task #10, to (commit all related files in directory `Ansible/`): - expose the application portal's IP (i.e, the load-balancer's) to the Internet via `socat` or other mechanism of your choice; - rebuild and push the application images to your Docker Hub @@ -367,7 +383,55 @@ You will get bonus for any of the following improvements. "nonce"](https://en.wikipedia.org/wiki/Cryptographic_nonce). +0.5 points. -### Testing +### Development + +#### Local installation + +To test your application before deploying it as a K8s service, you can install +and run it locally in your workstation. + +:bulb: It is strongly recommended to install Python's "virtualenv": + * [virtualenv](https://virtualenv.pypa.io/en/latest/index.html), and + * [virtualenvwrapper](https://virtualenvwrapper.readthedocs.io/en/latest/). + +At the top-level of your repo clone, get an overview of administrative +commands and options: + +``` shell +$ make help +``` + +Then, install the application in a virtual env (automatically created) + +``` shell +$ make vinstall +``` + +Otherwise, plain install (you might need to install python dependencies +manually): + +``` shell +$ make install +``` + +Finally, run the application in the current console: + +``` shell +$ make run +``` + +Or, run in another terminal: +``` shell +$ make trun +``` + + +#### Rebuild the Docker images + +:construction: **need details** + + +#### Test workflow The following tests shall be passed by your implementation: