diff --git a/Dockerfile b/Dockerfile index 91d72c2..9a63433 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,11 +15,11 @@ RUN pip install \ -r requirements.txt RUN pip install errbot[slack] -COPY data ./data COPY config.py . COPY errbot-slack-bolt-backend ./errbot-slack-bolt-backend COPY errbot-backend-botframework ./errbot-backend-botframework +RUN mkdir ./data RUN mkdir -p plugins/sdm COPY plugins/sdm ./plugins/sdm/ diff --git a/config.py b/config.py index d64eb3b..f67aa2e 100644 --- a/config.py +++ b/config.py @@ -16,24 +16,35 @@ def get_access_controls(): } def get_bot_identity(): - if os.getenv('SDM_BOT_PLATFORM') == 'ms-teams': + platform = os.getenv('SDM_BOT_PLATFORM') + if platform == 'ms-teams': return { "appId": os.getenv("AZURE_APP_ID"), "appPassword": os.getenv("AZURE_APP_PASSWORD") } + elif platform == 'slack-classic': + return { + 'token': os.getenv("SLACK_TOKEN") + } return { - "app_token": os.environ["SLACK_APP_TOKEN"], - "bot_token": os.environ["SLACK_BOT_TOKEN"], + "app_token": os.getenv("SLACK_APP_TOKEN"), + "bot_token": os.getenv("SLACK_BOT_TOKEN"), } def get_backend(): - if os.getenv('SDM_BOT_PLATFORM') == 'ms-teams': + platform = os.getenv('SDM_BOT_PLATFORM') + if platform == 'ms-teams': return 'BotFramework' + elif platform == 'slack-classic': + return 'Slack' return 'SlackBolt' def get_bot_extra_backend_dir(): - if os.getenv('SDM_BOT_PLATFORM') == 'ms-teams': + platform = os.getenv('SDM_BOT_PLATFORM') + if platform == 'ms-teams': return 'errbot-backend-botframework' + elif platform == 'slack-classic': + return None return 'errbot-slack-bolt-backend/errbot_slack_bolt_backend' diff --git a/docker-compose.yaml b/docker-compose.yaml index dd5306e..49fb85b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -2,15 +2,9 @@ version: "3.9" services: accessbot: image: public.ecr.aws/strongdm/accessbot:latest - environment: - # IMPORTANT: Do not enclose values in double or single quotes + env_file: + # You could use env-file.example as a reference + - env-file + ports: + - 3141:3141 - # Required variables - - SLACK_APP_TOKEN=slack-app-token - - SLACK_BOT_TOKEN=slack-bot-token - - SDM_ADMINS=@slack-handle1 @slack-handle2 # use space for configuring multiple slack handles - - SDM_API_ACCESS_KEY=sdm-api-acess-key - - SDM_API_SECRET_KEY=sdm-api-secret-key - - # Optional variables - # See: docs/CONFIGURE_ACCESSBOT.md diff --git a/docs/CONFIGURE_ACCESSBOT.md b/docs/CONFIGURE_ACCESSBOT.md index 01d0664..831b025 100644 --- a/docs/CONFIGURE_ACCESSBOT.md +++ b/docs/CONFIGURE_ACCESSBOT.md @@ -3,12 +3,21 @@ There are a number of variables you can use for configuring AccessBot. ## Required configuration -* **SLACK_APP_TOKEN**. Slack App-Level Token -* **SLACK_BOT_TOKEN**. Slack Bot User OAuth Token -* **SDM_ADMINS**. List of Slack admins, format: `@usernick`. Although it's not required, these users are often SDM admins too. You could use `whoami` for getting user nicks. +* **SDM_ADMINS**. List of Slack admins, format: `@usernick`. Although it's not required, these users are often SDM admins too. For getting user nicks, you could use the command `whoami` or the [tools/get-slack-handle.py](../tools/get-slack-handle.py) script. * **SDM_API_ACCESS_KEY**. SDM API Access Key * **SDM_API_SECRET_KEY**. SDM API Access Key Secret +### Slack (SDM_BOT_PLATFORM='slack' / default) +* **SLACK_APP_TOKEN**. Slack App-Level Token +* **SLACK_BOT_TOKEN**. Slack Bot User OAuth Token + +### Slack Classic (SDM_BOT_PLATFORM='slack-classic') +* **SLACK_TOKEN**. Slack Bot User OAuth Token for Classic Slack bot version + +### MS Teams (SDM_BOT_PLATFORM='ms-teams') +* **AZURE_APP_ID**. Set to the **Microsoft App ID** +* **AZURE_APP_PASSWORD**. Set to the **Secret Value** + ## Internal configuration * **LOG_LEVEL**. Logging level. Default = INFO * **SDM_DOCKERIZED**. Logging type. Default = true (_when using docker_), meaning logs go to STDOUT @@ -48,6 +57,7 @@ See image below for more information: `System Preferences > Keyboard > Text > Uncheck "Use smart quotes and dashes`. The `config` command fails to understand quotes as unicode characters. ### Using Tags +A snippet that might help: #### Allow Resource ``` diff --git a/docs/CONFIGURE_ALTERNATIVE_EMAILS.md b/docs/CONFIGURE_ALTERNATIVE_EMAILS.md index 64b3dc0..981d20d 100644 --- a/docs/CONFIGURE_ALTERNATIVE_EMAILS.md +++ b/docs/CONFIGURE_ALTERNATIVE_EMAILS.md @@ -2,7 +2,7 @@ You can make access requests using alternative emails. This functionality is specially helpful when you need to make access requests using a different email address than the one you have configured in your Slack Profile. -**_Custom profile fields are only available for Slack Business+ workspaces_** +**_Custom profile fields are only available for Slack Business+ workspaces and Bolt API_** Follow these steps to configure in your Slack Workspace: diff --git a/docs/CONFIGURE_LOCAL_ENV.md b/docs/CONFIGURE_LOCAL_ENV.md index 4558915..fe775da 100644 --- a/docs/CONFIGURE_LOCAL_ENV.md +++ b/docs/CONFIGURE_LOCAL_ENV.md @@ -11,19 +11,45 @@ pip install -r requirements/dev.txt ## Variables configuration ``` -export SLACK_APP_TOKEN=slack-app-token -export SLACK_BOT_TOKEN=slack-bot-token export SDM_API_ACCESS_KEY=api-access-key export SDM_API_SECRET_KEY=api-secret-key export SDM_ADMINS=@admin1 # if multiple, use: @admin1 @admin2 ``` -See [Configure Slack](CONFIGURE_SLACK.md) and [Configure SDM](CONFIGURE_SDM.md) +### BOT PLATFORM variables configuration + +See the subsessions about SDM_BOT_PLATFORM specific variables: + +#### SDM_BOT_PLATFORM is `slack` +``` +export SLACK_APP_TOKEN=slack-app-token +export SLACK_BOT_TOKEN=slack-bot-token +``` + +See [Configure Slack](CONFIGURE_SLACK.md) + +#### SDM_BOT_PLATFORM is `slack-classic` +``` +export SLACK_TOKEN=slack-token +``` + +See [Configure Slack Classic Bot](CONFIGURE_SLACK_CLASSIC.md) + +#### SDM_BOT_PLATFORM is `ms-teams`: +``` +export AZURE_APP_ID=app-id +export AZURE_APP_PASSWORD=app-password +``` + +See [Configure Microsoft Teams](CONFIGURE_MS_TEAMS.md) + +--- + +Before initialize errbot, you also need to [Configure SDM](CONFIGURE_SDM.md). ## Initialize errbot ``` mv config.py config.py.back -errbot --init pip install errbot[slack] mv config.py.back config.py ``` diff --git a/docs/CONFIGURE_SLACK_CLASSIC.md b/docs/CONFIGURE_SLACK_CLASSIC.md new file mode 100644 index 0000000..18abecf --- /dev/null +++ b/docs/CONFIGURE_SLACK_CLASSIC.md @@ -0,0 +1,31 @@ +# Configure Slack + +In order to configure AccessBot integration with Slack follow the next steps: + +1. Go to https://api.slack.com/apps?new_classic_app=1 and create a classic app + +![image](https://user-images.githubusercontent.com/313803/115708663-936d2380-a370-11eb-94d2-b5edb1596af7.png) + +2. Go to OAuth & Permissions and add bot scope in the Scopes + +![image](https://user-images.githubusercontent.com/313803/115709326-653c1380-a371-11eb-9346-f2fa81c7fd24.png) + +IMPORTANT: The reason why you need a classic app and the bot scope, is because the current AccessBot implementation uses the RTM API, which is not available +when updating to the new bot scopes. + +4. Go to App Home + +![image](https://user-images.githubusercontent.com/313803/115710249-6cafec80-a372-11eb-9071-bad38cf0d4bf.png) + +5. Click Add Legacy Bot User and set its name + +![image](https://user-images.githubusercontent.com/313803/115710432-a2ed6c00-a372-11eb-8fda-b8ef9c874e49.png) + +6. Go to Install App + +![image](https://user-images.githubusercontent.com/313803/115710557-c6181b80-a372-11eb-95dd-72927c81e53a.png) + + +**Use "Bot User OAuth Token" for your _SLACK_TOKEN_ variable** + +_Original instructions from [this thread](https://github.com/slackapi/python-slack-sdk/issues/609#issuecomment-6398872129)_ diff --git a/env-file.example b/env-file.example new file mode 100644 index 0000000..ac379ad --- /dev/null +++ b/env-file.example @@ -0,0 +1,42 @@ +# You can copy this file as "env-file" for your docker-compose +# IMPORTANT: Do not enclose values in double or single quotes + +# ------------------------------------------------------------------------------ +# | GENERAL ENV VARS | +# ------------------------------------------------------------------------------ +# These vars are required for any SDM_BOT_PLAFORM. + +SDM_BOT_PLATFORM=slack # possible values: slack, slack-classic, ms-teams +SDM_API_ACCESS_KEY= +SDM_API_SECRET_KEY= + +# ------------------------------------------------------------------------------ +# | SLACK BOLT ENV VARS | +# ------------------------------------------------------------------------------ +# You need to use the following vars when SDM_BOT_PLATFORM var is "slack": + +# SLACK_APP_TOKEN= +# SLACK_BOT_TOKEN= +# SDM_ADMINS=@nickname + +# ------------------------------------------------------------------------------ +# | SLACK CLASSIC ENV VARS | +# ------------------------------------------------------------------------------ +# You need to use the following vars when SDM_BOT_PLATFORM var is "slack-classic": + +# SLACK_TOKEN= +# SDM_ADMINS=@nickname + +# ------------------------------------------------------------------------------ +# | MS-TEAMS ENV VARS | +# ------------------------------------------------------------------------------ +# You need to use the following vars when SDM_BOT_PLATFORM var is "ms-teams": + +# SDM_ADMINS=user@email.com +# AZURE_APP_ID= +# AZURE_APP_PASSWORD= + +# ------------------------------------------------------------------------------ +# | OPTIONAL VARS | +# ------------------------------------------------------------------------------ +# See: docs/CONFIGURE_ACCESSBOT.md diff --git a/ms-teams/dev/start.sh b/ms-teams/dev/start.sh index ab6a7c4..70e7aff 100755 --- a/ms-teams/dev/start.sh +++ b/ms-teams/dev/start.sh @@ -2,7 +2,7 @@ errbot & pid[0]=$! -ssh -N -R 3141:localhost:3141 -i $LOG_EXPORT_CONTAINER_SSH_CREDENTIALS $LOG_EXPORT_CONTAINER_SSH_DESTINATION & +ssh -N -R 3141:localhost:3141 -i $ACCESSBOT_WEBHOOK_SSH_CREDENTIALS $ACCESSBOT_WEBHOOK_SSH_DESTINATION & pid[1]=$! trap "kill ${pid[0]} ${pid[1]}; exit 1" INT wait diff --git a/plugins/sdm/accessbot.py b/plugins/sdm/accessbot.py index a4cf1c4..cd03d16 100644 --- a/plugins/sdm/accessbot.py +++ b/plugins/sdm/accessbot.py @@ -238,8 +238,8 @@ def get_sdm_email_from_profile(self, sender, email_field): def clean_up_message(self, message): return self._platform.clean_up_message(message) - def format_access_request_params(self, resource_name, sender_nick, request_id): - return self._platform.format_access_request_params(resource_name, sender_nick, request_id) + def format_access_request_params(self, resource_name, sender_nick): + return self._platform.format_access_request_params(resource_name, sender_nick) def format_strikethrough(self, text): return self._platform.format_strikethrough(text) diff --git a/plugins/sdm/lib/helper/base_grant_helper.py b/plugins/sdm/lib/helper/base_grant_helper.py index 1393cd9..a7ae952 100644 --- a/plugins/sdm/lib/helper/base_grant_helper.py +++ b/plugins/sdm/lib/helper/base_grant_helper.py @@ -66,7 +66,6 @@ def __grant_access(self, message, sdm_object, sdm_account, execution_id, request self.__bot.log.info("##SDM## %s GrantHelper.__grant_%s sender_nick: %s sender_email: %s", execution_id, self.__grant_type, sender_nick, sender_email) self.__enter_grant_request(message, sdm_object, sdm_account, self.__grant_type, request_id) if not self.__needs_auto_approve(sdm_object) or self.__reached_max_auto_approve_uses(message.frm.person): - # TODO: ADD EXTRAS yield from self.__notify_access_request_entered(sender_nick, sdm_object.name, request_id, message) self.__bot.log.debug("##SDM## %s GrantHelper.__grant_%s needs manual approval", execution_id, self.__grant_type) return @@ -90,9 +89,9 @@ def __reached_max_auto_approve_uses(self, requester_id): def __notify_access_request_entered(self, sender_nick, resource_name, request_id, message): team_admins = ", ".join(self.__bot.get_admins()) operation_desc = self.get_operation_desc() - resource_name, sender_nick, request_id = self.__bot.format_access_request_params(resource_name, sender_nick, request_id) - yield f"Thanks {sender_nick}, that is a valid request. Let me check with the team admins: {team_admins}\nYour request id is {request_id}" - self.__notify_admins(f"Hey I have an {operation_desc} request from USER {sender_nick} for {self.__grant_type.name} {resource_name}! To approve, enter: **yes** {request_id}", message) + formatted_resource_name, formatted_sender_nick = self.__bot.format_access_request_params(resource_name, sender_nick) + yield f"Thanks {formatted_sender_nick}, that is a valid request. Let me check with the team admins: {team_admins}\nYour request id is **{request_id}**" + self.__notify_admins(f"Hey I have an {operation_desc} request from USER {formatted_sender_nick} for {self.__grant_type.name} {formatted_resource_name}! To approve, enter: **yes {request_id}**", message) def __notify_admins(self, text, message): admins_channel = self.__bot.config['ADMINS_CHANNEL'] diff --git a/plugins/sdm/lib/helper/poller_helper.py b/plugins/sdm/lib/helper/poller_helper.py index 6946f53..6577a23 100644 --- a/plugins/sdm/lib/helper/poller_helper.py +++ b/plugins/sdm/lib/helper/poller_helper.py @@ -9,7 +9,7 @@ def stale_grant_requests_cleaner(self): for request_id in self.__bot.get_grant_request_ids(): grant_request = self.__bot.get_grant_request(request_id) elapsed_time = time.time() - grant_request['timestamp'] - if elapsed_time > self.__bot.config['ADMIN_TIMEOUT']: + if elapsed_time >= self.__bot.config['ADMIN_TIMEOUT']: self.__bot.log.info("##SDM## Cleaning grant requests, stale request_id = %s", request_id) self.__notify_grant_request_denied(grant_request) self.__bot.remove_grant_request(request_id) diff --git a/plugins/sdm/lib/platform/base_platform.py b/plugins/sdm/lib/platform/base_platform.py index 68c091a..78c48b8 100644 --- a/plugins/sdm/lib/platform/base_platform.py +++ b/plugins/sdm/lib/platform/base_platform.py @@ -50,7 +50,7 @@ def clean_up_message(self, text): pass @abstractmethod - def format_access_request_params(self, resource_name, sender_nick, request_id): + def format_access_request_params(self, resource_name, sender_nick): pass @abstractmethod diff --git a/plugins/sdm/lib/platform/ms_teams_platform.py b/plugins/sdm/lib/platform/ms_teams_platform.py index e27137d..c897aa5 100644 --- a/plugins/sdm/lib/platform/ms_teams_platform.py +++ b/plugins/sdm/lib/platform/ms_teams_platform.py @@ -45,11 +45,8 @@ def get_user_nick(self, approver): def clean_up_message(self, text): return re.sub(r'.+', '', text).strip() - def format_access_request_params(self, resource_name, sender_nick, request_id): - resource_name = f'**{resource_name}**' - sender_nick = f'**{sender_nick}**' - request_id = f'**{request_id}**' - return resource_name, sender_nick, request_id + def format_access_request_params(self, resource_name, sender_nick): + return f'**{resource_name}**', f'**{sender_nick}**' def format_strikethrough(self, text): return r"~~" + text + r"~~" diff --git a/plugins/sdm/lib/platform/slack_platform.py b/plugins/sdm/lib/platform/slack_platform.py index e6a651e..1750d0d 100644 --- a/plugins/sdm/lib/platform/slack_platform.py +++ b/plugins/sdm/lib/platform/slack_platform.py @@ -40,11 +40,8 @@ def get_user_nick(self, approver): def clean_up_message(self, text): return text - def format_access_request_params(self, resource_name, sender_nick, request_id): - resource_name = r"\`" + resource_name + r"\`" - sender_nick = r"\`" + sender_nick + r"\`" - request_id = r"\`" + request_id + r"\`" - return resource_name, sender_nick, request_id + def format_access_request_params(self, resource_name, sender_nick): + return r"\`" + resource_name + r"\`", r"\`" + sender_nick + r"\`" def format_strikethrough(self, text): return r"~" + text + r"~" diff --git a/requirements/common.txt b/requirements/common.txt index 2d220ee..f157562 100644 --- a/requirements/common.txt +++ b/requirements/common.txt @@ -2,6 +2,7 @@ strongdm datetime errbot +slackclient slack-bolt shortuuid fuzzywuzzy