Authorization for Airflow 2.0 and AWS Cognito using roles - flask

i am deploying Airflow 2.3.0 with the official helm chart and using AWS Cognito for authentication. This works so far with the following webserver_config.py:
import sys
from tokenize import group
from airflow import configuration as conf
from airflow.www.security import AirflowSecurityManager
from flask_appbuilder.security.manager import AUTH_OAUTH
import logging
import os
import json
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
logger = logging.getLogger()
class CognitoSecurity(AirflowSecurityManager):
def oauth_user_info(self, provider, response=None):
if provider == "aws_cognito" and response:
logger.info(response)
res = self.appbuilder.sm.oauth_remotes[provider].get('oauth2/userInfo')
if res.raw.status != 200:
logger.error('Failed to obtain user info: %s', res.data)
return
me = json.loads(res._content)
logger.info(" user_data: %s", me)
return {"username": me.get("username"), "email": me.get("email")}
else:
return {}
AUTH_TYPE = AUTH_OAUTH
AUTH_ROLES_SYNC_AT_LOGIN = True # Checks roles on every login
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = "Admin"
COGNITO_URL = os.environ['COGNITO_URL']
CONSUMER_KEY = os.environ['CONSUMER_KEY']
SECRET_KEY = os.environ['SECRET_KEY']
REDIRECT_URI = os.environ['REDIRECT_URI']
JWKS_URI = ("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json"
% (os.environ['AWS_REGION'], os.environ['COGNITO_POOL_ID']))
OAUTH_PROVIDERS = [{
'name':'aws_cognito',
#'whitelist': ['#test.com'], # optional
'token_key':'access_token',
'url': COGNITO_URL,
'icon': 'fa-amazon',
'remote_app': {
'client_id': CONSUMER_KEY,
'client_secret': SECRET_KEY,
'base_url': os.path.join(COGNITO_URL, 'oauth2/idpresponse'),
"api_base_url": COGNITO_URL,
'redirect_uri' : REDIRECT_URI,
'jwks_uri': JWKS_URI,
'client_kwargs': {
'scope': 'email openid profile'
},
'access_token_url': os.path.join(COGNITO_URL, 'oauth2/token'),
'authorize_url': os.path.join(COGNITO_URL, 'oauth2/authorize')
}
}]
SECURITY_MANAGER_CLASS = CognitoSecurity
But now I'm trying to get the groups a user is in to map the groups to Airflow roles. And at this point I'm stuck and not getting anywhere.... Can someone help me with this?

It turned out that the method _azure_jwt_token_parse from the class BaseSecurityManager is misleading from the name. The parse function is not Azure specific and can therefore be used for other jwt token as well.
With the following webserver_config.py I can map the AWS Cognito groups to Airflow roles:
from signal import siginterrupt
import sys
from base64 import urlsafe_b64decode
from airflow import configuration as conf
from airflow.www.security import AirflowSecurityManager
from flask import session
from flask_appbuilder.security.manager import AUTH_OAUTH
import jwt
import logging
import os
import json
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(message)s")
logger = logging.getLogger()
class CognitoSecurity(AirflowSecurityManager):
def oauth_user_info(self, provider, response=None):
if provider == "aws_cognito" and response:
logger.debug(response)
res = self.appbuilder.sm.oauth_remotes[provider].get('oauth2/userInfo')
if res.raw.status != 200:
logger.error('Failed to obtain user info: %s', res.data)
return
me = json.loads(res._content)
#
decoded_token = self._azure_jwt_token_parse(response["id_token"])
logger.debug(" data: %s", decoded_token)
return {"username": me.get("username"),
"email": me.get("email"),
"role_keys": decoded_token.get("cognito:groups", ["Public"])
}
else:
return {}
AUTH_TYPE = AUTH_OAUTH
AUTH_ROLES_SYNC_AT_LOGIN = True # Checks roles on every login
AUTH_USER_REGISTRATION = True
#AUTH_USER_REGISTRATION_ROLE = "Admin"
AUTH_USER_REGISTRATION_ROLE = "Public"
AUTH_ROLES_MAPPING = {
"Viewer": ["User"],
"Admin": ["Admin"],
}
COGNITO_URL = os.environ['COGNITO_URL']
CONSUMER_KEY = os.environ['CONSUMER_KEY']
SECRET_KEY = os.environ['SECRET_KEY']
REDIRECT_URI = os.environ['REDIRECT_URI']
JWKS_URI = ("https://cognito-idp.%s.amazonaws.com/%s/.well-known/jwks.json"
% (os.environ['AWS_REGION'], os.environ['COGNITO_POOL_ID']))
OAUTH_PROVIDERS = [{
'name':'aws_cognito',
#'whitelist': ['#web.com'], # optional
'token_key':'access_token',
'url': COGNITO_URL,
'icon': 'fa-amazon',
'remote_app': {
'client_id': CONSUMER_KEY,
'client_secret': SECRET_KEY,
'base_url': os.path.join(COGNITO_URL, 'oauth2/idpresponse'),
"api_base_url": COGNITO_URL,
'redirect_uri' : REDIRECT_URI,
'jwks_uri': JWKS_URI,
'client_kwargs': {
'scope': 'email openid profile'
},
'access_token_url': os.path.join(COGNITO_URL, 'oauth2/token'),
'authorize_url': os.path.join(COGNITO_URL, 'oauth2/authorize')
}
}]
SECURITY_MANAGER_CLASS = CognitoSecurity

Related

Internal servor error response from the flask application

Hello i am writing the following code to authenticate the username and password and execute a entry method code if the given credentials are valid. But i am getting internal server error.Can someone help where it is getting wrong. My target is to execute a block of code if the credentials are matching.
#import statements
import Example
import Example2
import logging
from flask import Flask
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
#Creating the logge r variables and intialization
log=logging.getLogger()
format = "%(asctime)s %(message)s"
logging.basicConfig(format=format, level=logging.INFO, filename='Job_history_logs.log')
#Starting the Flask application
app = Flask(__name__)
auth = HTTPBasicAuth()
#users
users = {
"john": generate_password_hash("hello"),
"susan": generate_password_hash("bye")
}
#app.route('/todo/api/v1.0/tasks', methods=['GET'])
#auth.login_required
#auth.verify_password
def verify_password(username, password):
log.info("Username provided is "+ str(username))
log.info("password provided is "+ str(password))
if username in users:
log.info("Hash comparision is "+ str(check_password_hash(users.get(username), password)))
if check_password_hash(users.get(username), password):
return True
#auth.error_handler
def unauthorized():
return make_response(jsonify({'error': 'Unauthorized access'}), 401)
def entry():
result1 = Example.external()
result2 = Example2.external2()
log.info("result1 is "+str(result1))
log.info(str(result2))
return str(result1)+"...."+str(result2)
if __name__ == '__main__':
app.run(host='0.0.0.0')
I was able to run this application with simple changes in the sequence of usage of #auth_decorators.
working code
#import statements
import logging
from external import star_matrix
from flask_httpauth import HTTPBasicAuth
from flask import Flask
from werkzeug.security import generate_password_hash, check_password_hash
#Creating an object
log=logging.getLogger()
format = "%(asctime)s %(message)s"
logging.basicConfig(format=format, level=logging.INFO, filename='Job_history_logs.log')
app = Flask(__name__)
auth = HTTPBasicAuth()
#users
users = {
"john": generate_password_hash("hello"),
"susan": generate_password_hash("bye")
}
#auth.verify_password
def verify_password(username, password ):
log.info("Username provided is "+ str(username))
log.info("password provided is "+ str(password))
if username in users:
log.info("Hash comparision is "+ str(check_password_hash(users.get(username), password)))
if check_password_hash(users.get(username), password):
return True
#app.route('/todo/api/v1.0/tasks', methods=['GET'])
#auth.login_required
def entry():
log.info('inside the entry function')
result = star_matrix.external()
return result
if __name__ == '__main__':
app.run(host='0.0.0.0')

DRF - JWT How to fix Token still active after expired?

I'm using Djangorestframework with djangorestframework-simplejwt library, the token system is working except that after an access and refresh token are both expired ( I can confirm with postman ) the frontent app (Vue & axios) is able to still GET the updated data, how is this possible? When i check the axios request the token is the same as the one I use in postman, in Postman it gives me "Token Invalid or expired" but in axios it receives all the data and 200 OK.
These are the configs:
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework_simplejwt.authentication.JWTAuthentication',
)
}
SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=60),
'REFRESH_TOKEN_LIFETIME': timedelta(hours=24),
'ROTATE_REFRESH_TOKENS': True,
'BLACKLIST_AFTER_ROTATION': True,
'AUTH_HEADER_TYPES': ('JWT',),
'USER_ID_FIELD': 'id',
'USER_ID_CLAIM': 'user_id',
}
urls.py
from rest_framework_simplejwt.views import TokenRefreshView
from dgmon.views import MyTokenObtainPairView
app_name = 'dgmon'
admin.site.site_header = settings.ADMIN_SITE_HEADER
admin.site.site_title = settings.ADMIN_SITE_TITLE
urlpatterns = [
path('admin/', admin.site.urls),
re_path(r'^', include('dgmon.urls')),
path('api/token/', MyTokenObtainPairView.as_view(), name='token_obtain_pair'),
path('api/refresh/', TokenRefreshView.as_view(), name='token_refresh'),
]
views.py
from rest_framework_simplejwt.views import TokenObtainPairView
from dgmon.serializers import MyTokenObtainPairSerializer
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
serializers.py
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
def validate(self, attrs):
data = super().validate(attrs)
refresh = self.get_token(self.user)
data['refresh'] = str(refresh)
data['access'] = str(refresh.access_token)
data['user'] = self.user.username
data['groups'] = self.user.groups.values_list('name', flat=True)
return data

Why different error when using different network connection to put a file to a s3 bucket with Sign v4?

Here are my code:
First the driver script
#!/usr/bin/env python
import os
import sys
from gen_url import sign
import requests
import uuid
def upload_me(file_path, key=None, secret=None):
access_key = 'ABC' if key is None else key
secret_key = 'EDF' if secret is None else secret
s3_bucket = 'my-work-bucket'
object_name = '1-2-{uuid}.jpeg'.format(uuid=uuid.uuid4())
mime_type = 'image/jpeg'
expires = 24 * 60 * 60 # link expiry in sec
os.environ['AWS_ACCESS_KEY_ID'] = access_key
os.environ['AWS_SECRET_ACCESS_KEY'] = secret_key
region = 'us-west-2'
url = sign(key, secret, s3_bucket, object_name, mime_type, expires, region)
with open(file_path, 'r') as f:
resp = requests.post(url, data=f)
print resp.content
if __name__ == '__main__':
argc = len(sys.argv)
key = secret = None
if argc == 2 or argc == 4:
file_path = sys.argv[1]
if argc == 4:
key = sys.argv[2]
secret = sys.argv[3]
else:
raise Exception('Expect 1 or 3 arguments')
upload_me(file_path, key, secret)
The code of sign function in gen_url module
import sys
import boto3
from botocore.client import Config
from datetime import datetime, date, time
def sign(access_key, secret_key, s3_bucket, object_name, mime_type, expires, region):
s3_client = boto3.client('s3',
region_name=region,
aws_access_key_id=access_key,
aws_secret_access_key=secret_key)
# Don't include content type
# 'ContentType': mime_type
params = {
'Bucket': s3_bucket,
'Key': object_name,
}
response = s3_client.generate_presigned_url('put_object',
Params=params,
ExpiresIn=expires)
return response
When I am using the internet connection at home, it is the error I got:
requests.exceptions.ConnectionError: ('Connection aborted.', error(32, 'Broken pipe'))
But I use tethering with my iphone, the command gives a different error:
<Error><Code>SignatureDoesNotMatch</Code><Message>The request signature we calculated does not match the signature you provided. Check your key and signing method.</Message>
Why totally different output when the networks are different?
It turns out the last three lines of the driver script should be:
with open(file_path, 'rb') as f:
resp = requests.put(url, data=f)
print resp.content
Then there is no issue with either connections.
(error(32, 'Broken pipe') could be just a coincidence that there were connection issue with my ISP.

Lambda-API gateway : "message": "Internal server error"

I am using AWS CodeStar (Lambda + API Gateway) to build my serverless API. My lambda function works well in the Lambda console but strangely throws this error when I run the code on AWS CodeStar:
"message": "Internal server error"
Kindly help me with this issue.
import json
import os
import bz2
import pprint
import hashlib
import sqlite3
import re
from collections import namedtuple
from gzip import GzipFile
from io import BytesIO
from botocore.vendored import requests
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def handler(event, context):
logger.info('## ENVIRONMENT VARIABLES')
logger.info(os.environ)
logger.info('## EVENT')
logger.info(event)
n = get_package_list()
n1 = str(n)
dat = {"total_pack":n1}
return {'statusCode': 200,
'headers': {'Content-Type': 'application/json'},
'body': json.dumps(dat)
}
def get_package_list():
url = "http://amazonlinux.us-east-2.amazonaws.com/2/core/2.0/x86_64/c60ceaf6dfa3bc10e730c9e803b51543250c8a12bb009af00e527a598394cd5e/repodata/primary.sqlite.gz"
db_filename = "dbfile"
resp = requests.get(url, stream=True)
remote_data = resp.raw.read()
cached_fh = BytesIO(remote_data)
compressed_fh = GzipFile(fileobj=cached_fh)
with open(os.path.join('/tmp',db_filename), "wb") as local_fh:
local_fh.write(compressed_fh.read())
package_obj_list = []
db = sqlite3.connect(os.path.join('/tmp',db_filename))
c = db.cursor()
c.execute('SELECT name FROM packages')
for package in c.fetchall():
package_obj_list.append(package)
no_of_packages = len(package_obj_list)
return no_of_packages
Expected Result: should return an Integer (no_of_packages).

How to remove file after uploading it to Google Drive

I have written code in python that uploads a file to Google Drive, but after it uploads I cannot delete it from local drive, because I get error "Access Denied", but if I exit out of function then I can delete the file. So my question is how can I delete the file from inside the function?
GDriveUpload.py
import os
import httplib2
import ntpath
import oauth2client
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
# Copy your credentials here
_CLIENT_ID = 'YOUR_CLIENT_ID'
_CLIENT_SECRET = 'YOUR_CLIENT_SECRET'
_REFRESH_TOKEN = 'YOUR_REFRESH_TOKEN'
_PARENT_FOLDER_ID = 'YOUR_PARENT_FOLDER_ID'
_DATA_FILE = 'datafile.dat'
# ====================================================================================
# Upload file to Google Drive
def UploadFile(client_id, client_secret, refresh_token, parent_folder_id, local_file, DeleteOnExit=False):
cred = oauth2client.client.GoogleCredentials(None,client_id,client_secret,refresh_token,None,'https://accounts.google.com/o/oauth2/token',None)
http = cred.authorize(httplib2.Http())
drive_service = build('drive', 'v2', http=http)
media_body = MediaFileUpload(local_file, mimetype='application/octet-stream', chunksize=5242880, resumable=True)
body = {
'title': (ntpath.basename(local_file)),
'parents': [{'id': parent_folder_id}],
'mimeType': 'application/octet-stream'
}
request = drive_service.files().insert(body=body, media_body=media_body)
response = None
while response is None:
status, response = request.next_chunk()
if status:
print "Uploaded %.2f%%" % (status.progress() * 100)
if DeleteOnExit == True:
os.remove(local_file)
# ====================================================================================
if __name__ == '__main__':
UploadFile(_CLIENT_ID, _CLIENT_SECRET, _REFRESH_TOKEN, _PARENT_FOLDER_ID, _DATA_FILE, DeleteOnExit=True)