mirror of
https://github.com/Sea-Haven-Industries/afterhours-shift-manager.git
synced 2026-10-04 21:52:07 +00:00
Migrate secrets from SSM Parameter Store to Secrets Manager
- Slack bot token and signing secret now read from Secrets Manager - 3CX credentials (domain, client-id, client-secret) moved to Secrets Manager under afterhours-shift-manager/3cx-* prefix - Channel ID is now a non-secret CloudFormation parameter (ShiftChannel) - Add shared secrets.py helper for Secrets Manager reads - Remove SSM and KMS IAM policies, add secretsmanager:GetSecretValue
This commit is contained in:
parent
0fed60248a
commit
7979e2a4e7
5 changed files with 43 additions and 73 deletions
|
|
@ -10,31 +10,18 @@ import os
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from zoneinfo import ZoneInfo
|
from zoneinfo import ZoneInfo
|
||||||
|
|
||||||
import boto3
|
|
||||||
|
|
||||||
from shared.three_cx_client import ThreeCXClient
|
from shared.three_cx_client import ThreeCXClient
|
||||||
from shared.schedule import ShiftSchedule
|
from shared.schedule import ShiftSchedule
|
||||||
|
from shared.secrets import get_secret
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
||||||
EASTERN = ZoneInfo("America/New_York")
|
EASTERN = ZoneInfo("America/New_York")
|
||||||
|
|
||||||
# System extensions to exclude from roster sync
|
|
||||||
EXCLUDE_NAMES = {"Voicemail", "IVR", "Fax"}
|
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):
|
def handler(event, context):
|
||||||
now = datetime.now(EASTERN)
|
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()
|
"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(
|
client = ThreeCXClient(
|
||||||
domain=creds["domain"],
|
domain=get_secret(f"{secret_prefix}domain"),
|
||||||
auth_mode="oauth",
|
auth_mode="oauth",
|
||||||
client_id=creds["client_id"],
|
client_id=get_secret(f"{secret_prefix}client-id"),
|
||||||
client_secret=creds["client_secret"],
|
client_secret=get_secret(f"{secret_prefix}client-secret"),
|
||||||
)
|
)
|
||||||
|
|
||||||
members = client.get_group_members(group_name)
|
members = client.get_group_members(group_name)
|
||||||
|
|
|
||||||
16
src/shared/python/shared/secrets.py
Normal file
16
src/shared/python/shared/secrets.py
Normal file
|
|
@ -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"]
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import boto3
|
|
||||||
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
|
from slack_bolt.adapter.aws_lambda import SlackRequestHandler
|
||||||
|
|
||||||
from app import create_app
|
from app import create_app
|
||||||
|
from shared.secrets import get_secret
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
@ -16,23 +16,15 @@ logging.basicConfig(
|
||||||
format="%(asctime)s %(levelname)s %(name)s: %(message)s", level=logging.INFO
|
format="%(asctime)s %(levelname)s %(name)s: %(message)s", level=logging.INFO
|
||||||
)
|
)
|
||||||
|
|
||||||
# Lazy-initialized app singleton
|
|
||||||
_slack_handler = None
|
_slack_handler = None
|
||||||
|
|
||||||
|
|
||||||
def _get_handler() -> SlackRequestHandler:
|
def _get_handler() -> SlackRequestHandler:
|
||||||
global _slack_handler
|
global _slack_handler
|
||||||
if _slack_handler is None:
|
if _slack_handler is None:
|
||||||
ssm = boto3.client("ssm")
|
bot_token = get_secret(os.environ["SLACK_BOT_TOKEN_SECRET"])
|
||||||
bot_token = ssm.get_parameter(
|
signing_secret = get_secret(os.environ["SLACK_SIGNING_SECRET"])
|
||||||
Name=os.environ["SLACK_BOT_TOKEN_PARAM"], WithDecryption=True
|
schedule_channel = os.environ["SHIFT_CHANNEL"]
|
||||||
)["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"]
|
|
||||||
|
|
||||||
app = create_app(bot_token, signing_secret, schedule_channel=schedule_channel)
|
app = create_app(bot_token, signing_secret, schedule_channel=schedule_channel)
|
||||||
_slack_handler = SlackRequestHandler(app=app)
|
_slack_handler = SlackRequestHandler(app=app)
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ from slack_sdk import WebClient
|
||||||
|
|
||||||
from shared.blocks import build_pay_summary_blocks, build_week_schedule
|
from shared.blocks import build_pay_summary_blocks, build_week_schedule
|
||||||
from shared.schedule import FALLBACK_EXTENSION, ShiftSchedule
|
from shared.schedule import FALLBACK_EXTENSION, ShiftSchedule
|
||||||
|
from shared.secrets import get_secret
|
||||||
|
|
||||||
logger = logging.getLogger()
|
logger = logging.getLogger()
|
||||||
logger.setLevel(logging.INFO)
|
logger.setLevel(logging.INFO)
|
||||||
|
|
@ -147,13 +148,8 @@ def handler(event, context):
|
||||||
)
|
)
|
||||||
return {"skipped": True}
|
return {"skipped": True}
|
||||||
|
|
||||||
ssm = boto3.client("ssm")
|
bot_token = get_secret(os.environ["SLACK_BOT_TOKEN_SECRET"])
|
||||||
bot_token = ssm.get_parameter(
|
channel_id = os.environ["SHIFT_CHANNEL"]
|
||||||
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"]
|
|
||||||
|
|
||||||
schedule = ShiftSchedule()
|
schedule = ShiftSchedule()
|
||||||
slack = WebClient(token=bot_token)
|
slack = WebClient(token=bot_token)
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,9 @@ Parameters:
|
||||||
Timezone:
|
Timezone:
|
||||||
Type: String
|
Type: String
|
||||||
Default: "America/New_York"
|
Default: "America/New_York"
|
||||||
|
ShiftChannel:
|
||||||
|
Type: String
|
||||||
|
Description: Slack channel ID for schedule posts and shift notifications
|
||||||
SchedulerFunctionName:
|
SchedulerFunctionName:
|
||||||
Type: String
|
Type: String
|
||||||
Default: "3cx-ring-group-scheduler"
|
Default: "3cx-ring-group-scheduler"
|
||||||
|
|
@ -63,9 +66,9 @@ Resources:
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
SHIFT_TABLE: !Ref ShiftTable
|
SHIFT_TABLE: !Ref ShiftTable
|
||||||
SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token
|
SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token
|
||||||
SLACK_SIGNING_SECRET_PARAM: /afterhours-shift-manager/slack-signing-secret
|
SLACK_SIGNING_SECRET: afterhours-shift-manager/slack-signing-secret
|
||||||
SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id
|
SHIFT_CHANNEL: !Ref ShiftChannel
|
||||||
SCHEDULER_FUNCTION_NAME: !Ref SchedulerFunctionName
|
SCHEDULER_FUNCTION_NAME: !Ref SchedulerFunctionName
|
||||||
TZ: !Ref Timezone
|
TZ: !Ref Timezone
|
||||||
Policies:
|
Policies:
|
||||||
|
|
@ -74,16 +77,9 @@ Resources:
|
||||||
- Statement:
|
- Statement:
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- ssm:GetParameter
|
- secretsmanager:GetSecretValue
|
||||||
Resource:
|
Resource:
|
||||||
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/afterhours-shift-manager/*"
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*"
|
||||||
- Effect: Allow
|
|
||||||
Action:
|
|
||||||
- kms:Decrypt
|
|
||||||
Resource: "*"
|
|
||||||
Condition:
|
|
||||||
StringEquals:
|
|
||||||
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
|
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- lambda:InvokeFunction
|
- lambda:InvokeFunction
|
||||||
|
|
@ -108,8 +104,8 @@ Resources:
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
SHIFT_TABLE: !Ref ShiftTable
|
SHIFT_TABLE: !Ref ShiftTable
|
||||||
SLACK_BOT_TOKEN_PARAM: /afterhours-shift-manager/slack-bot-token
|
SLACK_BOT_TOKEN_SECRET: afterhours-shift-manager/slack-bot-token
|
||||||
SHIFT_CHANNEL_PARAM: /afterhours-shift-manager/channel-id
|
SHIFT_CHANNEL: !Ref ShiftChannel
|
||||||
SES_SENDER: noreply@seahaven.com
|
SES_SENDER: noreply@seahaven.com
|
||||||
PAYROLL_RECIPIENTS: payroll@seahaven.com
|
PAYROLL_RECIPIENTS: payroll@seahaven.com
|
||||||
PAY_REPORT_USER: U0A3SC48T47
|
PAY_REPORT_USER: U0A3SC48T47
|
||||||
|
|
@ -120,16 +116,9 @@ Resources:
|
||||||
- Statement:
|
- Statement:
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- ssm:GetParameter
|
- secretsmanager:GetSecretValue
|
||||||
Resource:
|
Resource:
|
||||||
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/afterhours-shift-manager/*"
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*"
|
||||||
- Effect: Allow
|
|
||||||
Action:
|
|
||||||
- kms:Decrypt
|
|
||||||
Resource: "*"
|
|
||||||
Condition:
|
|
||||||
StringEquals:
|
|
||||||
"kms:ViaService": !Sub "ssm.${AWS::Region}.amazonaws.com"
|
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- ses:SendEmail
|
- ses:SendEmail
|
||||||
|
|
@ -164,7 +153,7 @@ Resources:
|
||||||
Environment:
|
Environment:
|
||||||
Variables:
|
Variables:
|
||||||
SHIFT_TABLE: !Ref ShiftTable
|
SHIFT_TABLE: !Ref ShiftTable
|
||||||
TCX_SSM_PREFIX: /3cx-scheduler
|
TCX_SECRET_PREFIX: afterhours-shift-manager/3cx-
|
||||||
SYNC_GROUP: DEFAULT
|
SYNC_GROUP: DEFAULT
|
||||||
TZ: !Ref Timezone
|
TZ: !Ref Timezone
|
||||||
Policies:
|
Policies:
|
||||||
|
|
@ -173,19 +162,9 @@ Resources:
|
||||||
- Statement:
|
- Statement:
|
||||||
- Effect: Allow
|
- Effect: Allow
|
||||||
Action:
|
Action:
|
||||||
- ssm:GetParametersByPath
|
- secretsmanager:GetSecretValue
|
||||||
- ssm:GetParameter
|
|
||||||
- ssm:GetParameters
|
|
||||||
Resource:
|
Resource:
|
||||||
- !Sub "arn:aws:ssm:${AWS::Region}:${AWS::AccountId}:parameter/3cx-scheduler"
|
- !Sub "arn:aws:secretsmanager:${AWS::Region}:${AWS::AccountId}:secret:afterhours-shift-manager/*"
|
||||||
- !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"
|
|
||||||
Events:
|
Events:
|
||||||
# Daily at 6am ET (before the 7am schedule post and 8am 3CX scheduler)
|
# Daily at 6am ET (before the 7am schedule post and 8am 3CX scheduler)
|
||||||
# EST: 6am ET = 11:00 UTC (Nov-Mar)
|
# EST: 6am ET = 11:00 UTC (Nov-Mar)
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue