Skip to content
Snippets Groups Projects
Select Git revision
  • 96e66b1291f0e4aa4285045782aa863397adc956
  • main default protected
2 results

README.md

Blame
  • user avatar
    Marco Emilio "sphakka" Poleggi authored
    Signed-off-by: default avatarMarco Emilio "sphakka" Poleggi <marcoep@ieee.org>
    96e66b12
    History

    Project: web SSO service with K8s/KinD

    A pseudo web single-sing-on (SSO) service deployed on a Kubernets cluster provisioned with KinD. This is mostly an integration project with some application development.

    Introduction

    An SSO service allows users of a web platform, composed of different subsystems, to log in once onto any one of the subsystems and stay authenticated for a predefined amount of time (session). During a valid session, i.e., after the first log-in, users can access other subsystems without being asked for credentials.

    The idea is to implement a microservice deployed on a KinD cluster hosted by a single VM instance. The service shall be load-balanced by MetalLB and use some S3 storage for the enrollment and session data.

    Architecture

    The system's 3-tier architecture is microservice-based:

    1. The replicated front-end tier implements:
      • a login Web GUI composed of several views (languages: HTML5, JS) and
      • associated logic (languages: Python/Flask) which sends incoming authentication requests to the back-end and returns the results to the views. The ingress/egress point is a load balancer that exposes an external IP address.
    2. The back-end tier (languages: Python/Flask) tier receives and handles authentication requests from the front-end. All the enrollment and session logic is implemented here in a CRUD-like fashion: corresponding objects are stored in an S3-compatible storage.
    3. The storage tier is a standard S3-like object storage which can be accessed only by the back-end.

    Application's architecture and deployment Application's architecture and deployment schema

    Infrastructure and deployment

    The front-end replicas and the back-end are deployed in separate containers hosted by a Kubernetes-based MetalLB service deployment. The service, on its turn, is hosted by a 2-node KinD cluster installed on a single VM infrastructure.

    Front-end

    Web portal with REST-based functions, written in HTML5, JavaScript (views) and Python/Flask (main logic). All related files are in directory frontend/.

    The REST service routes are:

    • enroll: sign-up (subscribe) to the system with credentials
    • unenroll: delete subscription credentials (remove account)
    • login: authenticate with e-mail and, if needed (first login), password
    • logout: de-authenticate by removing the current session

    The following files in directory views/ handle the client-side workflow.

    • index.html provides just two buttons:
      • "Login" linked to the view login.html
      • "Sign up" (enroll) linked to the view signup.html
    • signup.html provides a form with with input fields "e-mail" and "password" and is linked to the dashboard.html view
    • login.html provides a form with input fields "e-mail" and "password" and is linked to the dashboard.html view.
    • dashboard.html provides two buttons "Logout" and "Remove account" (unenroll)

    The file main.py handles the REST logic by conveying all requests to the back-end.

    :bulb: This part does not requires adaptations.

    Back-end

    The single file backend/main.py (Python/Flask) implements the enrollment and session management logic by handling REST requests coming from the front-end. The corresponding objects are managed in a CRUD-like fashion in/from a single S3 storage bucket.

    :tools: This part requires some development. See the details in the boilerplate backend/main.py.

    Storage

    This is a single S3-like buckets with two directories: one for enrollment data, one for session data.

    Objects shall be written as JSON data based on the following proposed schema.

    Enrollment objects

    An enrollment object:

    • is named after the user e-mail
    • is unique in the system
    • is created when a new user subscribes to the system via the enroll function
    • is never updated (assume that users' passwords cannot be changed)
    • is deleted when a subscribed (== existing) user unsubscribes from the system via the unenroll function

    Minimum schema (you're free to extend it):

    {
    	"title": "Enrollment",
    	"type": "object",
    	"properties": {
    		"password": {
    			"type": "string",
    			"description": "The user's password."
    		},
    		"timestamp": {
    			"description": "The UNIX epoch time of object's creation",
    			"type": "integer",
    			"minimum": 0
    		}
    	}
    }

    Example data for object named foo@bar.com:

    {
    	"password": "SECRET",
    	"timestamp": 1733330967
    }

    Session objects

    A session object:

    • is named after the user e-mail
    • is unique in the system -- multiple sessions are not permitted
    • is created at the first user's connection to the system via the login function
    • is never updated
    • is deleted when a logged-in user disconnects from the system via the logout function

    Minimum schema (you're free to extend it):

    {
    	"title": "Session",
    	"type": "object",
    	"properties": {
    		"client": {
    			"description": "The client host's IPv4 address",
    			"type": "string",
    			"format": "ipv4"
    		},
    		"timestamp": {
    			"description": "The UNIX epoch time of object's creation",
    			"type": "integer",
    			"minimum": 0
    		}
    	}
    }

    Example data for object named foo@bar.com:

    {
    	"client": "192.168.1.2",
    	"timestamp": 1733330967
    }

    Workflows

    The following workflows correspond to the application "routes" discussed above. Additional information is provided in the boilerplate files. All involved data are in JSON format.

    Conventions:

    • KEYWORDS are capitalized.
    • Actors are in boldface.
    • 'CONSTANTS' are capitalized and single-quoted.
    • data items are lower-case and italicized.
    • all times are represented as UNIX epoch timestamps

    :bulb: For the sake of simplicity:

    • Sessions never expire.
    • No cookies are stored at the client.
    • The e-mail address is used as authentication token.

    All operations are stateless: apart from enrollment and session data, nothing else is recorded by the back-end.

    Enroll

    A new user subscribes to the system via the enroll function:

    1. User provides enrollment data (e-mail and password) via the the front-end's signup view.
    2. Front-end sends enrollment data to the back-end.
    3. Back-end verifies enrollment data:
      • IF user exists THEN returns 'KO:ALREADY_ENROLLED'
      • ELSE
        1. Writes enrollment data to storage
        2. Returns 'OK:ENROLLED' to the front-end
    4. Front-end receives response from the back-end and shows it to the user.

    Unenroll

    An enrolled user unsubscribes from the system (removes their account) with the unenroll function:

    1. User provides enrollment e-mail via the the front-end's dashboard view.
    2. Front-end sends enrollment data to the back-end.
    3. Back-end verifies enrollment data:
      • IF user does not exists THEN returns 'KO:NO_SUCH_USER'
      • ELSE
        1. Removes enrollment data and any active sessions from the storage
        2. Returns 'OK:UNENROLLED' to the front-end
    4. Front-end receives response from the back-end and shows it to the user.

    Login

    An enrolled user authenticates to the system with the login function:

    1. User provides e-mail via the the front-end's view login.
    2. Front-end sends e-mail to the back-end.
    3. Back-end verifies the e-mail:
      • IF user does not exists THEN returns 'KO:NO_SUCH_USER'
      • ELSE verifies sessions:
        1. IF an active session exists THEN returns 'OK:SESSION_EXISTS'
        2. ELSE returns 'OK:NEED_PASSWORD'.
    4. Front-end receives first response from the back-end:
      • IF response == 'OK:NEED_PASSWORD' THEN
        1. Prompts the user for their password.
        2. User provides password.
        3. Front-end sends e-mail and password to the back-end.
        4. Back-end verifies the password:
          • IF password matches THEN
            1. Writes a new session to storage.
            2. Returns 'OK:LOGGED_IN' to the front-end.
          • ELSE returns 'KO:WRONG_PASSWORD' to the front-end.
      • ELSE shows other response to the user and terminates.
    5. Front-end receives second response from the back-end and shows it to the user.

    Logout

    An enrolled user deauthenticates to the system with the logout function:

    1. User provides e-mail via the the front-end's view dashboard.
    2. Front-end sends e-mail to the back-end.
    3. Back-end verifies the e-mail:
      • IF user does not exists THEN returns 'KO:NO_SUCH_USER'
      • ELSE verifies sessions:
        • IF an active session exists THEN
          1. Removes session from storage.
          2. Returns 'OK:LOGGED_OUT'.
        • ELSE returns 'KO:NO_ACTIVE_SESSION'.
    4. Front-end receives response from the back-end and shows it to the user.

    Infrastructure provisioning

    The infrastructure is entirely hosted by SwitchEngines (OpenStack cloud) and is composed of

    • A single VM featuring:
      • Source image: A Debian 12 Bookworm
      • Flavor: 2 vCPUs, 4GB RAM, 40GB root disk -- no extra volume needed
      • A KinD/Kubectl installation
    • One S3 bucket.

    The infrastructure (computing instance + S3 storage) shall be provisioned via Terraform.

    :bulb: References:

    Service deployment

    The service shall be deployed on a 3-pods K8s microservice hosted by a two-node KinD cluster, with a single MetalLB load-balancer entry point, as done with the Lab-K8s. The front-end shall be replicated over 2 pods. The 3rd pod shall host the back-end.

    The whole software stack, apart from the KinD package, shall be deployed via Ansible. Of course, instead of the dummy http-echo app, two different Docker images shall be used -- :construction: see the project's Docker file boilerplate: one for the front-end, the other for the back-end, both hosted in the Docker Hub registry -- you shall create a personal public repository. We trust you, please, do not cheat!

    The front-end image does not need to be rebuilt, unless you want to implement some client-side (HTML/JS) bonuses.

    The back-end image shall be rebuilt after any modification to the application code.

    The whole stack shall be redeployed whenever any of its images are updated.

    Tasks

    :construction: To be finalized

    :warning: Please respect the file layout provided by this repository!

    You shall:

    1. Fork this repository.
    2. Complete the Python back-end file(s) in folder Application/backend/main.py.
    3. Rebuild the application back-end Docker image, and push it to your public Docker Hub repositry -- (: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 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/.
    5. Complete your Ansible playbook, starting from the version you developed in Lab-Ansible Task #10, to (commit all realted 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 repository. These shall be local_action tasks.

    :bulb: References:

    Bonuses

    You will get bonus for any of the following improvements.

    • Ask for the password when removing an account (unenroll) -- extra safety. +0.2 points.
    • Use password hashing in the back-end -- extra security. +0.1 points.
    • Handle session expiration after a configurable amount of time (in minutes). You can use an extra enrollment view's parameter expiration_time. +0.3 points.
    • Handle multiple sessions started from different browsers, e.g., private navigation tab/window. +0.4 points.
    • Support temporary disconnection via client session data stored in a Web cookie (without the password) -- this requires some sort of cryptographic "nonce". +0.5 points.

    Testing

    The following tests shall be passed by your implementation:

    • Starting with a non-provisioned infrastructure:
      1. Terraform apply shall provision a bare-bone VM and an S3 bucket
      2. Ansible-playbook shall install KinD/Kubectl and deploy your load-balanced application
      3. Your application shall be reachable on port 80 (or another of your choice) from any host outside the Cloud network.
    • Once your application is installed, you shall exercise all the branch conditions described by the above workflows:
      1. Enroll a new user: shall succeed
      2. Enroll an existing: user shall fail
      3. Unenroll a new user: shall fail
      4. Unenroll an existing user: shall succeed
      5. Login an enrolled user: shall succeed and ask for the password
        • with a valid password: shall succeed
        • with an invalid password: shall fail
      6. Login a non-enrolled user: shall fail
      7. Logout a non-enrolled user: shall fail
      8. Logout an enrolled user
        • with an active session: shall succeed
        • without an active session: shall fail