-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlessons_learned.txt
2203 lines (1519 loc) · 83.4 KB
/
lessons_learned.txt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Tech that we will be using to build this project:
python
docker
kubernetes
mongodb
RabbitMQ
What we are trying to achieve?
User Request -->1. Use authentication service to authenticate
--> API Gateway -->3. Puts a message in RabbitMQ --> letting downstream services to know that there is a video to be processed in mongoDB
-->2. Stores the video in Mongo --> converter consumes messages from queue and also fetch the id of video from it (convert it) --> puts the message on queue --> notification service --> emails user--> user gives id and JWT to API (requests audio)--> API gateway fetch the audio from mongodb and servers the user.
Installations:
1. docker https://docs.docker.com/get-docker/
2. kubectl https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
3. k9s https://github.com/derailed/k9s
4. mysql Can install using package manager like: brew install mysql or choco install mysql for production use best security practices while installing
5. python https://www.python.org/downloads/
6. minikube https://minikube.sigs.k8s.io/docs/start/
directory structure : microservicesk8s\python\src\
now code for different microservices inside src directory under different directories.
Auth service
_______________________________________________________________________________________________________________________________________________________________________________________________________________________
1. microservicesk8s\python\src\
mkdir auth
cd auth
python3 -m venv venv | create virtual env
It is a common practice to use virtual environments to create isolated Python environments for projects, allowing you to manage project-specific dependencies and avoid conflicts with other Python installations.
To activate the virtual environment, you can use the following command:
On Linux/Mac: source venv/bin/activate
On Windows: .\venv\Scripts\activate.bat
source venv/bin/activate | activate the virtual env
pip install pylint | Pylint is a widely used tool for analyzing Python code and identifying potential issues, enforcing coding standards, and providing suggestions for improvement.
pip install jedi | It's designed to enhance the development experience by providing intelligent code completion, offering insights into the structure and properties of Python code, and suggesting improvements.
pip install pyjwt
pip install flask
pip install flask_mysqldb
5.server.py for authentication service
_______________________________________
vi server.py
# Import necessary libraries
import jwt # Import JWT (JSON Web Token), it is used for securely transmitting information between parties as a compact, self-contained, and digitally signed token.
import datetime # Import datetime library for handling dates and times
import os # Import os library for interacting with the operating system
from flask import Flask, request # Import Flask framework for building web applications
from flask_mysqldb import MySQL # Import MySQL extension for Flask to work with MySQL databases
# Create a Flask web application instance This basically configure our servers so that requests to different routes can interface with our code.
server = Flask(__name__)
# Connect Flask application to MySQL database
mysql = MySQL(server)
# Configure the MySQL database connection settings using environment variables(these are set in secret and configmap resources)
# This is where the database connection details are stored securely
server.config["MYSQL_HOST"] = os.environ.get("MYSQL_HOST") # Database host address
server.config["MYSQL_USER"] = os.environ.get("MYSQL_USER") # Database username
server.config["MYSQL_PASSWORD"] = os.environ.get("MYSQL_PASSWORD") # Database password
server.config["MYSQL_DB"] = os.environ.get("MYSQL_DB") # Database name
server.config["MYSQL_PORT"] = os.environ.get("MYSQL_PORT") # Database port number
# Define a route for the "/login" URL using HTTP POST method
@server.route("/login", methods=["POST"])
def login():
auth = request.authorization # Retrieve credentials from the request(imported in begining) -->from Basic Authentication header
# Check if credentials are missing
if not auth:
return "Missing credentials", 401 # Return an error response with status code 401
#check db for username and password
__________________________________________________________________________
testing by setting MYSQL_HOST=localhost
using env variables to set mysql database connection configuration
terminal: export MYSQL_HOST=localhost
(venv) root@PC-f7ad11:/mnt/c/Users/LENOVO/microservicesk8s/python/src/auth# python3 server.py
localhost
__________________________________________________________________________
6.vi init.sql Creating a database so as to check username and password for user trying to login
-- Create a new user 'auth_user' who can access the database only from 'localhost' and set their password
CREATE USER 'auth_user'@'localhost' IDENTIFIED BY 'Aauth123'; ------------------> this is for querying the database not the username and password which are saved inside database.
-- Create a new database named 'auth'
CREATE DATABASE auth;
-- Grant all privileges on the 'auth' database to the user 'auth_user' only from 'localhost'
GRANT ALL PRIVILEGES ON auth.* TO 'auth_user'@'localhost';
-- Switch to using the 'auth' database for subsequent operations
USE auth;
-- Create a new table named 'user' with columns: 'id', 'email', and 'password'
CREATE TABLE user (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, -- Create an auto-incrementing unique identifier
email VARCHAR(255) NOT NULL, -- Column to store email addresses (limited to 255 characters), cannot be empty
password VARCHAR(255) NOT NULL -- Column to store passwords (limited to 255 characters), cannot be empty
);
-- Insert a new record into the 'user' table with specified email and password values
INSERT INTO user (email, password) VALUES ('[email protected]', 'interview'); ---------------------> this is the user that goes into our database and gets verified to get access to our api gateway
_________________________________________________________________________
7. I had populated the mysql once with incorrect email, when retried got an error:
PS C:\Users\LENOVO\microservicesk8s> Get-Content .\init.sql | mysql -uroot (in linux, we can use mysql -uroot < init.sql )
ERROR 1064 (42000) at line 1: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'CREATE DATABASE auth' at line 3
Deleted the database and user
mysql -uroot -e "DROP DATABASE auth"
and also the user: mysql -uroot -e "DROP USER auth_user@localhost"
PS C:\Users\LENOVO\microservicesk8s> mysql -uroot
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.31 MySQL Community Server - GPL
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases; show databases; to check the list of databases present in mysql
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
4 rows in set (0.06 sec)
mysql> mysql -uroot < init.sql
So, when you run this command, it will connect to the MySQL server as the root user and execute the SQL statements present in the init.sql file.
This is commonly used to initialize or set up a database with the required schema, tables, and data.
Make sure you are in the appropriate directory where the init.sql file is located before running the command.
Also, be cautious when using the root user for regular tasks, as it has elevated privileges.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| auth |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
PS C:\Users\LENOVO\microservicesk8s\python\src\auth> mysql -uroot
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 28
Server version: 8.0.31 MySQL Community Server - GPL
Copyright (c) 2000, 2022, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| auth |
| information_schema |
| mysql |
| performance_schema |
| sys |
+--------------------+
5 rows in set (0.00 sec)
mysql> show tables;
ERROR 1046 (3D000): No database selected
mysql> use auth; use database_name; for selecting a database
Database changed
mysql> show tables; show tables; for getting the list of tables in the selected database
+----------------+
| Tables_in_auth |
+----------------+
| user |
+----------------+
1 row in set (0.02 sec)
mysql> describe user;
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| email | varchar(255) | NO | | NULL | |
| password | varchar(255) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
3 rows in set (0.02 sec)
mysql> select * user;
ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'user' at line 1
mysql> select * from user;
+----+-------------------------+-----------+
| id | email | password |
+----+-------------------------+-----------+
| 1 | [email protected] | interview | --------------------> username and password that we had given in our init.sql
+----+-------------------------+-----------+
1 row in set (0.00 sec)
_______________________________________________________________________________
email VARCHAR(255) NOT NULL UNIQUE, update init.sql -------------> add unique for username as we can't have multiple usernames with the same password attached to them
mysql -uroot -e "DROP DATABASE auth"
mysql -uroot -e "DROP USER auth_user@localhost"
+----------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+----------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| email | varchar(255) | NO | UNI | NULL | | ----> unique added
| password | varchar(255) | NO | | NULL | |
+----------+--------------+------+-----+---------+----------------+
_______________________________________________________________________
8.
Now we will be adding code for checking the db for username and password: we will do this by using flask_mysql db
that will use all the config we added as environment variables such as to host = localhost, port = 3306 so as to connect to database.
A cursor is an object that enables you to interact with the database and retrieve, manipulate, and process data from the result sets of SQL queries.
A cursor acts as a pointer or iterator that allows you to navigate through the rows of a query result, one row at a time.
cur = mysql.connection.cursor()
# Execute a SQL query to retrieve email and password from the 'user' table based on the provided username
res = cur.execute(
"SELECT email, password FROM user WHERE email=%s", (auth.username,) --------- here auth.username is coming from what is passed in your request authentication header (we defined this under login route)
)
# Check if the query result contains any rows
if res > 0:
# Fetch the first row from the query result
user_row = cur.fetchone()
# Extract the email and password from the fetched row
email = user_row[0]
password = user_row[1]
# Compare the provided username and password with the retrieved ones
if auth.username != email or auth.password != password:
# Return an error message and HTTP status code 401 (Unauthorized)
return "invalid credentials", 401
else:
# If credentials are valid, create a JSON Web Token (JWT) using the provided username and JWT secret
return createJWT(auth.username, os.environ.get("JWT_SECRET"), JWT)
else:
# If no matching rows were found, return an error message and HTTP status code 401 (Unauthorized)
return "invalid credentials", 401
__________________________________________________________________________________
Now in the last part we understood if creds passed match with the creds in mysql, we have to create a JWT
Jason Web Tokens
Understanding the reason of using it:
Our microservices are going to be running in a k8s cluster and that cluster is not going to be accessible to or from the outside internet.
Our client is going to be making request from outside the cluster with the intention of making use of our distributed system deployed within our pvt k8s cluster via our system's gateway.
Our gateway service is going to be the entrypoint to the overall application
The gateway receives request from the client and also communicates with a necessary internal service to complete the request that came from the client.
for example for uploading a file we need to create a upload endpoint in our file that makes all internal services to work so that it can be processed
so if our internal services live in a private network how do we determine when should we allow request from internet??
This is where auth service comes in!
requester's creds are saved in authdb
when their username,password matched with our mysql db
we give them access!
What is Basic authentication?
This scheme requires the client to provide a username and password in a request which should be contained in a header field
of the form
Authorization: Basic <credentials>
where credentials = base64(username:password)
we take these and compare it with our mysqldb
If we find a match we return JWT
which client will use for subsequent request to our gateway upload and download endpoints
JWT looks like:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
3 parts seperated by a . <----fullstop
Header: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 is the base64Url-encoded header. This header specifies that the token is signed using the HMAC SHA256 algorithm (HS256) and that it's a JWT (JWT).
Payload: eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ is the base64Url-encoded payload. This payload contains claims about the user, such as the subject (sub), name (name), and issued at time (iat).
Signature: SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c is the signature. It's generated by hashing the encoded header and payload along with a secret key using the HMAC SHA256 algorithm.
Base64 encoding converts the binary data into text format, which is passed through communication channel where a user can handle text safely.
Base64 is also called as Privacy enhanced Electronic mail (PEM) and is primarily used in email encryption process.
Two json formatted strings and a signature
What are these 3 parts?
1. header
contains a key value pair for signing algorithm(algorithm which was used to sign the token which will allow us to verify later that the sender of the token is who it said it is and to ensure that the message was not changed along the way and the type of token which is JWT
asymmitric and | symmetric signing algorithms
both pub & pvt keys | one pvt key
ours will use symmetric algo : HS256
Once token is signed by this key and then sent to user and user makes request back it will send its JWT in request and our auth service can validate using single private key if
the token has been tampered or signed with diff key our auth service will know it is invalid.
Its imp that we know that json formatted data in our token is invalid cause that data is going to contain the access permissions for the user
2. payload
claims under payload are pieces of info about the user mostly defined by us, there are predefined claims as well
3. signature
HMACSHA256(
base64UrlEncode(header)
base64UrlEncode(payload)
our pvt key
)
and signing them with our symmetric algorithm
from claims we can check the access level of the client
for this project we will allow all the endpoints to our client if his payload section contains "admin": true
_____________Function to create the json webtoken__________________________________________________________________________________________________
# Define a function named createJWT that takes three parameters: username, secret, and authz.
def createJWT(username, secret, authz):
# Inside the function, create a JSON Web Token (JWT) by using the jwt.encode function.
return jwt.encode(
# Create the content (payload) of the JWT as a dictionary.
{
# Include a "user" key in the payload and assign it the value of the 'username' parameter.
"user": username,
# Include an "exp" (expiration) key in the payload and set it to the current date and time,
# increased by one day. This is when the JWT will expire.
"exp": datetime.datetime.now(tz=datetime.timezone.utc) + datetime.timedelta(days=1),
# Include an "iat" (issued at) key in the payload and set it to the current date and time in UTC.
# This is when the JWT is issued.
"iat": datetime.datetime.utcnow(),
# Include an "admin" key in the payload and assign it the value of the 'authz' parameter.
"admin": authz ---------------> when creating the token this will determine wether user have access to endpoint or not
},
# Pass the payload as the first argument to jwt.encode.
# Pass the 'secret' parameter as the second argument to jwt.encode.
secret,
# Specify the hashing algorithm 'HS256' for encoding the JWT.
# End of the jwt.encode function.
algorithm='HS256',
)
# End of the createJWT function.
___________________________________________________________________________________________________________
# Compare the provided username and password with the retrieved ones
if auth.username != email or auth.password != password:
# Return an error message and HTTP status code 401 (Unauthorized)
return "invalid credentials", 401
else:
# If credentials are valid, create a JSON Web Token (JWT) using the provided username and JWT secret
return createJWT(auth.username, os.environ.get("JWT_SECRET"), True) ----------> here is the secret which is also used to validate the jwt
else:
# If no matching rows were found, return an error message and HTTP status code 401 (Unauthorized)
return "invalid credentials", 401
___________________________________________________________________________________________________________
Understanding how traffic will connect to our application running inside docker container.
https://flask.palletsprojects.com/en/1.1.x/quickstart/#a-minimal-application
if __name__ == "__main__":
server.run(host="0.0.0.0", port=5000)
# Specify the hashing algorithm 'HS256' for encoding the JWT.
# End of the jwt.encode function.
algorithm='HS256',
)
# End of the createJWT function.
if __name__ == "__main__":
server.run(host="0.0.0.0", port=5000) <---------------------- What is this in the end of the code of server.py ?? Let's understand
In Python, the if __name__ == "__main__": construct is commonly used to determine whether a Python script is being run directly or imported as a module into another script.
Code inside this block is executed only if the script is the main program, allowing you to organize your code and control its execution.
# The "__name__" special variable is a built-in Python variable that holds the name of the current module or script.
# The double underscores before and after "name" are a Python convention for special variables.
if __name__ == "__main__":
server.run(host="0.0.0.0", port=5000)
# You can think of this as the "entry point" for your script.
# Here, it's using the Flask framework's "server.run" method to start a web server.
# The server is configured to listen on all available network interfaces ("0.0.0.0")
# and port 5000
server.run(host="0.0.0.0", port=5000)
# This line starts the Flask web server when the script is executed directly.
explains host=5000 and more
because we want our application to have requests from any ip if not given it will be only available from our localhost
our application will be running inside docker container, container have its own ip which is in its own docker network
Requests are sent on containers ip
But this is not enough for our flask application to receive those requests
We need to tell our flask app to listen on our containers ip
This is where host config comes in
Host is the server that is hosting our application in our case it is docker container so we need to tell our flask to listen on docker container ip but
our docker contianer ip address is subject to change so instead of setting it to static ip of docker container we set it to 0.0.0.0
It is wild card which tells flask app to listen on any and all of our container's ip addresses it can find
if we dont configure this it will default to localhost which is loopback address
and localhost is only accessible from within the host therefore outside request sent to our docker container will neverfrom outside world will never make it to our flask app
because loopback address is not publically accessible so wehen we set host to 0.0.0.0 we are telling our app to listen on all of our docker container ips including loop back address our local host
and any other ipaddress available on our docker container
for example if we connect our docker container to 2 seperate docker networks
docker will assign a diff ipaddress to our container for each docker network that means that with 0.0.0.0 host config our flask app will listen to requests coming to both of the ip address assigned to the container, its also possible to set the host config to a specific ipaddress
_____________________________________________________________________________________________________________________
create route to validate JWT's https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication (for understanding different schemes like basic auth, bearer etc)
______________________________
and this route is going to used by our api gateway to validate JWT's to sync with request from the client to both upload and recieve or download mp3s
@server.route("/validate", method=["POST"])
def validate():
encoded_jwt = request.header["Authorization"]
if not encoded_jwt:
return "missing credentials", 401
If you remember from our basic authorization scheme
For that scheme we need to have word basic in our authorization header that contain our base64(username:password)
Authorization: Basic base64(username:password)
for our JWT we instead need to have word bearer in our header!
Authorization: Bearer<token>
format for authorization header
Authorization: <type> <credentials>
type represents authentication scheme and creds represents creds for that particular type
if type is basic we know base64 encoded creds
if type is bearer we know that party isin possesion of the token has access to the token associated resources
Now in the code for our validation endpoint to save some time we are going to assume the authorization header contains a bearer token
therefore we are not going to validate or check the word that represents the type that comes before the credentials in the header
in actual prod env you should spend time to check the extra time to check the type or the authentication scheme presents within the authorization header
code in server.py for validating
__________________________________
# It specifies that this route will handle HTTP POST requests at the "/validate" URL.
@server.route("/validate", method=["POST"])
def validate():
# Extract the JWT (JSON Web Token) from the "Authorization" header of the HTTP request.
encoded_jwt = request.headers["Authorization"]
# Check if the JWT is missing in the request headers.
if not encoded_jwt:
# If the JWT is missing, return an error response with a status code 401 (Unauthorized).
return "missing credentials", 401
# Split the JWT string to extract the actual token part (the part after "Bearer ").
encoded_jwt = encoded_jwt.split(" ")[1]
# Try to decode the JWT using a secret key specified in an environment variable.
try:
decoded = jwt.decode(
encoded_jwt, os.environ.get("JWT_SECRET"), algorithm=["HS256"]
)
except:
# If decoding fails, return an error response with a status code 403 (Forbidden).
return "not authorized", 403
# If the JWT is successfully decoded, return the decoded contents as a response with a status code 200 (OK).
return decoded, 200
_____________________________________________________________________________________________________________________done with auth service code
Now Dockerfile for authentication service
FROM python:3.10-slim-bullseye
# Install system dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends --no-install-suggests \
build-essential default-libmysqlclient-dev pkg-config \
&& pip install --no-cache-dir --upgrade pip
# Set the working directory
WORKDIR /app
# Copy the requirements file and install dependencies
COPY ./requirements.txt /app/ ----> cp this before copying the whole code makes sense as any change in source code shouldn't make us downloaded dependencies again for no reason.(Only built again if change in dependencies)
RUN pip install --no-cache-dir --requirement /app/requirements.txt
# Copy the rest of your application code
COPY . /app/
EXPOSE 5000 -------------> this is just for the sake of documentation, for selecting the port we use -p flag in command
# Define the command to run your application
CMD ["python3", "server.py"]
docker login
docker build -t bhanumalhotra/auth1:latest .
docker push bhanumalhotra/auth1:latest (you don't need to specially go to dockerhub and create repository, once login you can directly push)
Build speed is important to consider!
Each layer in dockerfile is built on top of the previous one and docker uses cache layers if nothing is changed in the layer. But it builds each layer after the layer with a change.
____________________________________________________________________________________________________________________
Kubernetes manifests
https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
mkdir manifests
cd manifests and create the following files
auth-deploy.yml
apiVersion: apps/v1
kind: Deployment
metadata:
name: auth
labels:
app: auth
spec:
replicas: 2
selector:
matchLabels:
app: auth
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 3
template:
metadata:
labels:
app: auth
spec:
containers:
- name: auth
image: bhanumalhotra/auth1
ports:
- containerPort: 5000
envFrom:
- configMapRef:
name: auth-configmap these will be picked by the shell and populated as environment variables
- secretRef:
name: auth-secret
Rolling Update Deployment
The Deployment updates Pods in a rolling update fashion when .spec.strategy.type==RollingUpdate. You can specify maxUnavailable and maxSurge to control the rolling update process.
________________________________________________________________________________________________________
configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: auth-configmap
data:
MYSQL_HOST: host.minikube.internal -----------------> these will be picked by the shell as env variables. these are for connecting the auth service to mysql db which contains user and password and is running on our localhost
MYSQL_USER: auth_user
MYSQL_DB: auth
MYSQL_PORT: "3306"
The host.minikube.internal domain is a special DNS name that resolves to the host machine's IP address from within a Minikube cluster.
It is commonly used when you want to access services running on your host machine from within a Minikube cluster.
as our sql is running on our local system!
________________________________________________________________________________________________________
secret.yml
apiVersion: v1
kind: Secret
metadata:
name: auth-secret
stringData:
MYSQL_PASSWORD: Auth123 #should not be shared on git in general
JWT_SECRET: interview #should not be shared on git in general
type: Opaque
these are set as env variables as it is given in pod config.
You can also set them as mounts, example:
spec:
containers:
- name: my-container
image: my-image
volumeMounts:
- name: secret-volume
mountPath: /etc/my-secrets
volumes:
- name: secret-volume
secret:
secretName: my-secret
_______________________________________________________________________________________________________
apiVersion: v1
kind: Service
metadata:
name:
spec:
selector:
app: auth
type: ClusterIP -------> for internal communication in cluster
ports:
- port: 5000
targetPort: 5000
protocol: TCP
______________________________________________________________________________________________________
minikube delete (if in case it is giving errors)
minikube start
_____________
(venv) PS C:\Users\LENOVO\microservicesk8s\python\src\auth\manifests> kubectl apply -f ./
deployment.apps/auth unchanged
configmap/auth-configmap unchanged
secret/auth-secret configured
service/auth created
kuberenetes is smart, it will throw the errors in configurations once you try applying them.
after applying the files
Crashbackloopoff (common error)
error was related to how routes are defined in the app, we need to use methods and not method.
return f(self, *args, **kwargs) ││ File "/usr/local/lib/python3.10/site-packages/flask/app.py", line 1052, in add_url_ ││ rule = self.url_rule_class(rule, methods=methods, **options) ││ TypeError: Rule.__init__() got an unexpected keyword argument 'method'
resolved
______________________________________________
Inside the container using k9s
<<K9s-Shell>> Pod: default/auth-5c9cb5887-5xbm2 | Container: auth
root@auth-5c9cb5887-5xbm2:/app# printenc
bash: printenc: command not found
root@auth-5c9cb5887-5xbm2:/app# printenv
KUBERNETES_SERVICE_PORT_HTTPS=443
MYSQL_PORT=3306
KUBERNETES_SERVICE_PORT=443
HOSTNAME=auth-5c9cb5887-5xbm2
PYTHON_VERSION=3.10.13
AUTH_PORT_5000_TCP_PORT=5000
AUTH_PORT_5000_TCP=tcp://10.106.214.73:5000
MYSQL_DB=auth
AUTH_SERVICE_PORT=5000
PWD=/app
PYTHON_SETUPTOOLS_VERSION=65.5.1
MYSQL_PASSWORD=Auth123
MYSQL_USER=auth_user
HOME=/root
LANG=C.UTF-8
KUBERNETES_PORT_443_TCP=tcp://10.96.0.1:443
GPG_KEY=A035C8C19219BA821ECEA86B64E628F8D684696D
AUTH_SERVICE_HOST=10.106.214.73
AUTH_PORT_5000_TCP_PROTO=tcp
MYSQL_HOST=host.minikube.internal
AUTH_PORT_5000_TCP_ADDR=10.106.214.73
TERM=xterm
SHLVL=1
KUBERNETES_PORT_443_TCP_PROTO=tcp
PYTHON_PIP_VERSION=23.0.1
KUBERNETES_PORT_443_TCP_ADDR=10.96.0.1
AUTH_PORT=tcp://10.106.214.73:5000
PYTHON_GET_PIP_SHA256=45a2bb8bf2bb5eff16fdd00faef6f29731831c7c59bd9fc2bf1f3bed511ff1fe
KUBERNETES_SERVICE_HOST=10.96.0.1
KUBERNETES_PORT=tcp://10.96.0.1:443
KUBERNETES_PORT_443_TCP_PORT=443
PYTHON_GET_PIP_URL=https://github.com/pypa/get-pip/raw/9af82b715db434abb94a0a6f3569f43e72157346/public/get-pip.py
PATH=/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
JWT_SECRET=interview
_=/usr/bin/printenv
root@auth-5c9cb5887-5xbm2:/app#
_______________________________________________________________________________________________________
Understanding k8s
docker start container, docker stop container, docker kill container, docker restart container etc No need
auto healing, auto scaling, single host problem are resolved by k8s
A Kubernetes object is a "record of intent"--once you create the object, the Kubernetes system will constantly work to ensure that object exists.
By creating an object, you're effectively telling the Kubernetes system what you want your cluster's workload to look like; this is your cluster's desired state.
say you need 4 contianer not 1 now, manually you will deploy 3 more containers and then attach them to the load balancer
kubectl scale deployment --replicas=4 deployment_name
The kubernetes control plane continually and actively manages every objects's actual state to match the desired state you supplied.
To work with kuberenetes objects- wether to create, modify or delete them you'll need to use kubectl(kubernetes api)
https://kubernetes.io/docs/reference/ ------------> great place to learn everything about k8s
https://kubernetes.io/docs/concepts/overview/components/ ----------> architecture of k8s
In the .yaml file for the Kubernetes object you want to create, you'll need to set values for the following fields:
apiVersion - Which version of the Kubernetes API you're using to create this object
kind - What kind of object you want to create
metadata - Data that helps uniquely identify the object, including a name string, UID, and optional namespace
spec - What state you desire for the object
__________________________________________________________________________________________________________________________________________________________________________________________________________________
GATEWAY: THE ENTRYPOINT
installations:
100 cd gateway
101 python -m venv venv
102 .\venv\Scripts\Activate.ps1
106 pip install jedi
107 pip install pylint
108 pip install pika
109 pip install flask
110 pip install pyMongo
111 pip install Flask-PyMongo
gridfs ?
what is it?
we are using mongodb to store our files mp3 and videos
Limitsize on on document size of mongodb is 16mb
for storing larger size of document mongoDB provides GridFS
https://www.mongodb.com/docs/manual/core/gridfs/
GridFS uses two collection to store files. One collection stores the file chunks (255kB), other stores file metadata
When you request for data, driver reassembles the chunks
______________________________________________________________________________________________
server.py
# Import necessary libraries/modules
import os # Import the operating system module for various system-related functions
import gridfs # Import the GridFS module for storing and retrieving large files in MongoDB
import pika # Import the Pika module for interacting with RabbitMQ, a message broker
import json # Import the JSON module for working with JSON data
from flask import Flask, request # Import the Flask web framework and request object
# Import custom modules
from auth import validate # Import a function called 'validate' from a module named 'auth' ---------> we will create these custom modules soon
from auth_svc import access # Import a function called 'access' from a module named 'auth_svc'
from storage import util # Import a module named 'util' from a package named 'storage'
# Create a Flask web application instance
server = Flask(__name__)
# Configure the MongoDB URI (Uniform Resource Identifier) for the server's MongoDB connection
server.config["MONGO_URI"] = "mongodb://host.minikube.internal:27017/videos" ---------------------------> host.minikube.internal takes us to localhost
# Create a connection to the MongoDB database using Flask-PyMongo extension
mongo = PyMongo(server)
# Create a GridFS instance for working with files stored in MongoDB
fs = gridfs.GridFS(mongo.db)
# Create a connection to the RabbitMQ message broker
connection = pika.BlockingConnection(pika.ConnectionParameters("rabbitmq"))
# Create a communication channel for sending and receiving messages in RabbitMQ
channel = connection.channel()
# Define a route for handling HTTP POST requests to "/login"
@server.route("/login", methods=["POST"])
def login():
# Call the 'login' function from the 'access' module with the 'request' object and store the result in 'token' and 'err' ---------------------------> access.py is called which is a package we will be building in auth_svc directory
token, err = access.login(request) # This line calls a function to handle user login and stores the result in 'token' and 'err'
if not err:
return token # Return the 'token' as a response if there are no errors
else:
return err # Return the 'err' message as a response if there are errors
@server.route("/upload", methods=["POST"])
def upload():
access, err = validate.token(request) # This line calls a function to validate a user token and stores the result in 'access' and 'err' ----------->validate.py is called and its response will be the payloads in json format, check the validate.yml response in the end.
access = json.loads(access) #converts jason to python. Now this access will have payload which also have admin is set to true or false.
if access["admin"]: ------------------------------> now from the payload we check if it contains admin or not, this says if admin claim resolves to true then we give the access.
if len(request.files) > 1 or len(request.files) < 1:
return "exactly 1 file required", 400 #this request.file dictionary will have key for the file and file as a value. _ is key and f is file
for _, f in request.files.items():
err = util.upload(f, fs, channel, access) ------------------> we will create this util function futher(uploading the file to mongodb)
if err:
return err
return "success!", 200
else:
return "not authorized", 401
@server.route("/download", methods=["GET"])
def download():
pass -------------------------------------> passing the download function for now, will build it once we reach the stage of downloading the files
if __name__ == "__main__":
server.run(host="0.0.0.0", port=8080)
_____________________________________________________________________________
mkdir auth_svc (under gateway)
create __init__.py file which will mark this directory as package
vim access.py ------------> module that contains our login function and calls authentication service for checking username and password in mysqldb
# Import necessary libraries/modules
import os # Import the operating system module for various system-related functions
import requests # Import the 'requests' module for making HTTP requests to an internal authentication service
# Define a function named 'login' that takes a 'request' object as an argument
def login(request):
# Extract the 'authorization' header from the 'request' object