From 1c20b4ba58d6b5e9b6a97e7fd0f6939610e63e09 Mon Sep 17 00:00:00 2001 From: Frederik Ring Date: Sun, 14 Jul 2019 11:50:13 +0200 Subject: [PATCH] add basic auth authorizer for admin --- .circleci/config.yml | 3 ++ accounts/authorizer/__init__.py | 58 +++++++++++++++++++++++++++++++++ accounts/lambda | 0 accounts/serverless.yml | 37 +++++++++++++++++++-- 4 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 accounts/authorizer/__init__.py create mode 100644 accounts/lambda diff --git a/.circleci/config.yml b/.circleci/config.yml index a5e558c..fc6445f 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -290,6 +290,9 @@ jobs: - run: name: Install dependencies command: npm install + - run: + name: Install psycopg2 dependencies + command: sudo apt-get install libpq-dev - save_cache: paths: - ~/offen/packages/node_modules diff --git a/accounts/authorizer/__init__.py b/accounts/authorizer/__init__.py new file mode 100644 index 0000000..964bea6 --- /dev/null +++ b/accounts/authorizer/__init__.py @@ -0,0 +1,58 @@ +import base64 +from os import environ + +from passlib.hash import bcrypt + + +def build_api_arn(method_arn): + arn_chunks = method_arn.split(":") + aws_region = arn_chunks[3] + aws_account_id = arn_chunks[4] + + gateway_arn_chunks = arn_chunks[5].split("/") + rest_api_id = gateway_arn_chunks[0] + stage = gateway_arn_chunks[1] + + return "arn:aws:execute-api:{}:{}:{}/{}/*/*".format( + aws_region, aws_account_id, rest_api_id, stage + ) + +def build_response(api_arn, allow): + effect = "Deny" + if allow: + effect = "Allow" + + return { + "principalId": "offen", + "policyDocument": { + "Version": "2012-10-17", + "Statement": [ + { + "Action": ["execute-api:Invoke"], + "Effect": effect, + "Resource": [api_arn] + } + ] + } + } + + +def handler(event, context): + api_arn = build_api_arn(event["methodArn"]) + + auth_string = base64.standard_b64decode(event["authorizationToken"].lstrip("Basic ")).decode() + if not auth_string: + return build_response(api_arn, False) + + credentials = auth_string.split(":") + user = credentials[0] + password = credentials[1] + + if user != environ.get("BASIC_AUTH_USER"): + return build_response(api_arn, False) + + hashed_password = base64.standard_b64decode(environ.get("HASHED_BASIC_AUTH_PASSWORD")).decode() + if not bcrypt.verify(password, hashed_password): + return build_response(api_arn, False) + + return build_response(api_arn, True) diff --git a/accounts/lambda b/accounts/lambda new file mode 100644 index 0000000..e69de29 diff --git a/accounts/serverless.yml b/accounts/serverless.yml index a81ad85..17b3d15 100644 --- a/accounts/serverless.yml +++ b/accounts/serverless.yml @@ -54,20 +54,53 @@ custom: fileName: requirements.txt functions: + authorizer: + handler: authorizer.handler + environment: + BASIC_AUTH_USER: ${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/basicAuthUser~true} + HASHED_BASIC_AUTH_PASSWORD: ${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/hashedBasicAuthPassword~true} app: handler: wsgi_handler.handler events: + - http: + path: /admin/ + method: any + authorizer: + name: authorizer + resultTtlInSeconds: 0 + identitySource: method.request.header.Authorization + - http: + path: /admin/{proxy+} + method: any + authorizer: + name: authorizer + resultTtlInSeconds: 0 + identitySource: method.request.header.Authorization - http: path: '/' method: any - http: - path: '{proxy+}' + path: '/{proxy+}' method: any environment: CORS_ORIGIN: https://${self:custom.origin.${self:custom.stage}} COOKIE_DOMAIN: ${self:custom.cookieDomain.${self:custom.stage}} JWT_PRIVATE_KEY: '${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/jwtPrivateKey~true}' JWT_PUBLIC_KEY: '${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/jwtPublicKey~true}' - HASHED_PASSWORD: ${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/hashedBasicAuthPassword~true} + HASHED_BASIC_AUTH_PASSWORD: ${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/hashedBasicAuthPassword~true} + BASIC_AUTH_USER: ${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/basicAuthUser~true} SESSION_SECRET: '${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/sessionSecret~true}' SERVER_URL: ${self:custom.serverHost.${self:custom.stage}} + POSTGRES_CONNECTION_STRING: '${ssm:/aws/reference/secretsmanager/${self:custom.stage}/accounts/postgresConnectionString~true}' + +resources: + Resources: + GatewayResponse: + Type: 'AWS::ApiGateway::GatewayResponse' + Properties: + ResponseParameters: + gatewayresponse.header.WWW-Authenticate: "'Basic'" + ResponseType: UNAUTHORIZED + RestApiId: + Ref: 'ApiGatewayRestApi' + StatusCode: '401'