diff --git a/src/roster-sync/app.py b/src/roster-sync/app.py index fa53420..935884c 100644 --- a/src/roster-sync/app.py +++ b/src/roster-sync/app.py @@ -10,31 +10,18 @@ import os from datetime import datetime from zoneinfo import ZoneInfo -import boto3 - from shared.three_cx_client import ThreeCXClient from shared.schedule import ShiftSchedule +from shared.secrets import get_secret logger = logging.getLogger() logger.setLevel(logging.INFO) EASTERN = ZoneInfo("America/New_York") -# System extensions to exclude from roster sync EXCLUDE_NAMES = {"Voicemail", "IVR", "Fax"} -def get_3cx_credentials() -> dict: - ssm = boto3.client("ssm") - prefix = os.environ.get("TCX_SSM_PREFIX", "/3cx-scheduler") - params = ssm.get_parameters_by_path(Path=prefix, WithDecryption=True) - creds = {} - for p in params["Parameters"]: - key = p["Name"].split("/")[-1] - creds[key] = p["Value"] - return creds - - def handler(event, context): now = datetime.now(EASTERN) @@ -52,12 +39,12 @@ def handler(event, context): "Starting roster sync from 3CX group '%s' at %s", group_name, now.isoformat() ) - creds = get_3cx_credentials() + secret_prefix = os.environ["TCX_SECRET_PREFIX"] client = ThreeCXClient( - domain=creds["domain"], + domain=get_secret(f"{secret_prefix}domain"), auth_mode="oauth", - client_id=creds["client_id"], - client_secret=creds["client_secret"], + client_id=get_secret(f"{secret_prefix}client-id"), + client_secret=get_secret(f"{secret_prefix}client-secret"), ) members = client.get_group_members(group_name) diff --git a/src/shared/python/shared/secrets.py b/src/shared/python/shared/secrets.py new file mode 100644 index 0000000..82f312a --- /dev/null +++ b/src/shared/python/shared/secrets.py @@ -0,0 +1,16 @@ +"""Fetch secrets from AWS Secrets Manager.""" + +import boto3 + +_client = None + + +def _get_client(): + global _client + if _client is None: + _client = boto3.client("secretsmanager") + return _client + + +def get_secret(secret_id: str) -> str: + return _get_client().get_secret_value(SecretId=secret_id)["SecretString"] diff --git a/src/slack-bot/handler.py b/src/slack-bot/handler.py index 32fb37c..3a3e2fc 100644 --- a/src/slack-bot/handler.py +++ b/src/slack-bot/handler.py @@ -3,10 +3,10 @@ import logging import os -import boto3 from slack_bolt.adapter.aws_lambda import SlackRequestHandler from app import create_app +from shared.secrets import get_secret logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -16,23 +16,15 @@ logging.basicConfig( format="%(asctime)s %(levelname)s %(name)s: %(message)s", level=logging.INFO ) -# Lazy-initialized app singleton _slack_handler = None def _get_handler() -> SlackRequestHandler: global _slack_handler if _slack_handler is None: - ssm = boto3.client("ssm") - bot_token = ssm.get_parameter( - Name=os.environ["SLACK_BOT_TOKEN_PARAM"], WithDecryption=True - )["Parameter"]["Value"] - signing_secret = ssm.get_parameter( - Name=os.environ["SLACK_SIGNING_SECRET_PARAM"], WithDecryption=True - )["Parameter"]["Value"] - schedule_channel = ssm.get_parameter( - Name=os.environ["SHIFT_CHANNEL_PARAM"], WithDecryption=True - )["Parameter"]["Value"] + bot_token = get_secret(os.environ["SLACK_BOT_TOKEN_SECRET"]) + signing_secret = get_secret(os.environ["SLACK_SIGNING_SECRET"]) + schedule_channel = os.environ["SHIFT_CHANNEL"] app = create_app(bot_token, signing_secret, schedule_channel=schedule_channel) _slack_handler = SlackRequestHandler(app=app) diff --git a/src/weekly-post/app.py b/src/weekly-post/app.py index b9abb1b..a630210 100644 --- a/src/weekly-post/app.py +++ b/src/weekly-post/app.py @@ -12,6 +12,7 @@ from slack_sdk import WebClient from shared.blocks import build_pay_summary_blocks, build_week_schedule from shared.schedule import FALLBACK_EXTENSION, ShiftSchedule +from shared.secrets import get_secret logger = logging.getLogger() logger.setLevel(logging.INFO) @@ -147,13 +148,8 @@ def handler(event, context): ) return {"skipped": True} - ssm = boto3.client("ssm") - bot_token = ssm.get_parameter( - Name=os.environ["SLACK_BOT_TOKEN_PARAM"], WithDecryption=True - )["Parameter"]["Value"] - channel_id = ssm.get_parameter( - Name=os.environ["SHIFT_CHANNEL_PARAM"], WithDecryption=True - )["Parameter"]["Value"] + bot_token = get_secret(os.environ["SLACK_BOT_TOKEN_SECRET"]) + channel_id = os.environ["SHIFT_CHANNEL"] schedule = ShiftSchedule() slack = WebClient(token=bot_token) diff --git a/template.yaml b/template.yaml index 2f94e60..654b847 100644 --- a/template.yaml +++ b/template.yaml @@ -6,6 +6,9 @@ Parameters: Timezone: Type: String Default: "America/New_York" + ShiftChannel: + Type: String + Description: Slack channel ID for schedule posts and shift notifications SchedulerFunctionName: Type: String Default: "3cx-ring-group-scheduler" @@ -63,9 +66,9 @@ Resources: Environment: Variables: SHIFT_TABLE: !Ref ShiftTable - SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token - SLACK_SIGNING_SECRET_PARAM: /afterhours-shift-manager/slack-signing-secret - SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id + SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token + SLACK_SIGNING_SECRET: afterhours-shift-manager/slack-signing-secret + SHIFT_CHANNEL: !Ref ShiftChannel SCHEDULER_FUNCTION_NAME: !Ref SchedulerFunctionName TZ: !Ref Timezone Policies: @@ -74,16 +77,9 @@ Resources: - Statement: - Effect: Allow Action: - - ssm:GetParameter + - secretsmanager:GetSecretValue Resource: - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/afterhours-shift-manager/*" - - Effect: Allow - Action: - - kms:Decrypt - Resource: "*" - Condition: - StringEquals: - "kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com" + - !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*" - Effect: Allow Action: - lambda:InvokeFunction @@ -108,8 +104,8 @@ Resources: Environment: Variables: SHIFT_TABLE: !Ref ShiftTable - SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token - SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id + SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token + SHIFT_CHANNEL: !Ref ShiftChannel SES_SENDER: noreply@seahaven.com PAYROLL_RECIPIENTS: payroll@seahaven.com PAY_REPORT_USER: U0A3SC48T47 @@ -120,16 +116,9 @@ Resources: - Statement: - Effect: Allow Action: - - ssm:GetParameter + - secretsmanager:GetSecretValue Resource: - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/afterhours-shift-manager/*" - - Effect: Allow - Action: - - kms:Decrypt - Resource: "*" - Condition: - StringEquals: - "kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com" + - !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*" - Effect: Allow Action: - ses:SendEmail @@ -164,7 +153,7 @@ Resources: Environment: Variables: SHIFT_TABLE: !Ref ShiftTable - TCX_SSM_PREFIX: /3cx-scheduler + TCX_SECRET_PREFIX: afterhours-shift-manager/3cx- SYNC_GROUP: DEFAULT TZ: !Ref Timezone Policies: @@ -173,19 +162,9 @@ Resources: - Statement: - Effect: Allow Action: - - ssm:GetParametersByPath - - ssm:GetParameter - - ssm:GetParameters + - secretsmanager:GetSecretValue Resource: - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/3cx-scheduler" - - !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/3cx-scheduler/*" - - Effect: Allow - Action: - - kms:Decrypt - Resource: "*" - Condition: - StringEquals: - "kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com" + - !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*" Events: # Daily at 6am ET (before the 7am schedule post and 8am 3CX scheduler) # EST: 6am ET = 11:00 UTC (Nov-Mar)