Slack Integration
AlmaForge can post a Slack message for every access request in your cluster and update that message when the request's state changes. When a request is created in AlmaForge, a notification appears in the configured channel or direct message. When the request is approved, denied, or expires, the same message is updated in place with the resolution and the reviewer's context.
The integration is built into almad. You do not install
a separate plugin, run a callback server, or expose a public URL to
Slack. Configuration lives entirely in a SlackConnector resource. Fill
it in under Integrations in the Web UI, or write the YAML
yourself and run alma apply. Both paths store the same
resource. The plugin polls it once a minute, so applying, editing,
or deleting a connector takes effect within about 60 seconds with
no daemon restart.
The current release pushes AlmaForge access-request state into Slack. Clicking a button in Slack does not approve or deny the AlmaForge access request. Approvals must be made in AlmaForge. Slack interactive callbacks are out of scope for this release.
Prerequisites
Before you start, make sure you have:
- A running AlmaForge cluster with an Auth Service that you can
reach over the network and a working
almaCLI pointed at it. - A requester allowed to request
accessand a different user allowed to review that request. See Just-in-Time Access. - A Slack workspace where you have permission to install apps.
- A dedicated Slack app for AlmaForge with a Bot User. The bot
needs these OAuth scopes at minimum:
chat:writeto post and update access-request messages.users:readandusers:read.emailto resolve AlmaForge logins to Slack users for direct messaging and message attribution.im:writeto open direct-message conversations with reviewers and requesters whenroleToRecipientsroutes to a user instead of a channel.
- The bot invited to each destination channel. A Slack bot can only post to channels
it has been
/invited into. The integration never auto-joins. If you reference a channel inroleToRecipientsand forget to invite the bot, the plugin logs anot_in_channelerror and the message is not delivered.
SlackConnector resource reference
The integration is configured by a single resource of kind
SlackConnector. It has no top-level fields beyond the standard
metadata block. Everything is in spec.
| Field | Required | Description |
|---|---|---|
spec.botToken | yes | Slack Bot User OAuth Token (starts with xoxb-). Keep it inline for a quick test, or load it from external secret storage. See Resource value expansion for the supported forms. |
spec.usernameClaim | no | Which AlmaForge user field to match against Slack. One of email (default) or username. Either way the plugin sends the AlmaForge login: with email it passes the login to Slack's users.lookupByEmail, so mentions resolve only where logins are email addresses. With username it matches the login against Slack handles. |
spec.roleToRecipients | yes | Map from AlmaForge role name to a list of Slack destinations. Each destination is a channel name, with or without the leading hash (approvals and #approvals both work), a channel ID (C0123456789), or an email address, which is resolved to a direct message with that Slack user. Any other value is passed to Slack unchanged as the target channel. The special key "*" is the default recipient list used for any requested role that is not matched by a more specific entry. It is required. |
The connector resource name (metadata.name) must be exactly
slack. The integration reads only this name and ignores any
SlackConnector applied under a different name.
Step 1. Create a Slack app for AlmaForge
Sign in to api.slack.com/apps and create a new app From scratch.
Name it something that makes its purpose obvious in your workspace.
AlmaForge Access Requests is a good default. Pick the workspace
the integration should post into.
Under OAuth & Permissions, add the bot scopes listed in the
prerequisites: chat:write, users:read, users:read.email, and
im:write. Install the app into your workspace when prompted.
Slack will mint a Bot User OAuth Token (begins with xoxb-) and
show it on the OAuth & Permissions page. Store it in your secret manager. Access to stored connector secrets
requires permission to read secrets.
Step 2. Invite the bot to its channels
For each channel listed in roleToRecipients, invite the bot user
with /invite @AlmaForge Access Requests.
This is the single most common source of "why isn't the integration posting?" tickets. A bot cannot post to a channel it has not joined, and Slack does not surface this until the first post attempt fails.
Step 3. Apply a SlackConnector resource
Write a YAML file describing the integration. The simplest form embeds the bot token inline. This is appropriate for a quick demo or a lab cluster.
apiVersion: almaforge.com/v1
kind: SlackConnector
metadata:
name: slack
spec:
# Replace every value below before applying.
botToken: xoxb-000000000000-000000000000-replace-me
usernameClaim: email
roleToRecipients:
# '*' is the default recipient and is required.
'*':
values: ['#access-requests']
For production, keep the secret out of the manifest and load it
from a file or environment variable instead. See
Resource value expansion
for the exact syntax and field support. Rotate the token by
updating the underlying secret and re-applying the connector.
No almad restart is required.
Apply the resource:
alma apply -f slack.yamlThe single connector name slack is the well-known identity the
integration looks for. Additional SlackConnector resources with
different names are accepted by the API but never read, so stick to
one. The plugin only ever fetches the well-known name, so an extra
connector is silent rather than logged.
Step 4. Verify the integration
The plugin polls the SlackConnector resource once a minute and
reconciles its state against what it observes. It starts on first
appearance, restarts on resource-version change, and stops on
delete. Apply, edit, and delete are all picked up within about 60
seconds with no daemon restart.
Tail the almad logs on your auth server and look for the
plugin's start lines:
journalctl -u almad -f | grep slack_service... slack_service: connector found; starting plugin recipient_roles=2... Plugin is readyIf you see an error instead, the most common causes are:
invalid_auth/ 401 from Slack: the bot token is wrong, has been rotated, or the Slack app has been uninstalled from the workspace.not_in_channel: the bot has not been/invited to one of the channels inroleToRecipients. Invite it and wait for the next polling tick.channel_not_found: a channel name inroleToRecipientsdoes not exist in the workspace, or the bot cannot see it. Private channels need an explicit invite to become visible.no such file or directoryorpermission denied: the file named by a${file:PATH}reference inbotTokendoes not exist or is not readable by root. A file over the 64 KiB cap is reported asexceeds 65536 bytes.
Now create a real access request and confirm that a Slack message appears in the target channel:
alma request create --roles access --nowait --reason "slack integration smoke test"As a different user with permission to review this request, approve or deny it in AlmaForge. Use the request ID printed by the create command:
alma request review <request-id> --approve --reason "smoke test ok"The matching Slack message updates in place to show the resolution, the reviewer's name, and the resolution reason. Each review is also posted as a threaded reply to the original message, so operators can read the full decision history without expanding the main message.
How AlmaForge matches requesters to Slack users
When AlmaForge posts a Slack message for an access request, it
tries to mention the Slack user who requested access by linking the
AlmaForge login to a Slack account. The lookup strategy depends on
the connector's usernameClaim.
email(default): the plugin passes the AlmaForge login to Slack'susers.lookupByEmail. If a Slack user with that address exists in the workspace, the message mentions them via<@U0123456>. The login is sent as written, so this resolves only when AlmaForge logins are themselves email addresses. That is the common case for clusters that authenticate through an SSO provider whose email addresses match their Slack accounts.username: the plugin takes the AlmaForge login name verbatim and tries to match it against Slack handles. This is the right choice when AlmaForge logins are short machine names and the matching Slack handles are chosen to match them.
If no Slack user is found, the message is still posted. The
requester is identified by their AlmaForge login name in the
message body and no Slack @mention is generated.
The same lookup runs against each reviewer's login when an approve or deny reply is posted, so approver mentions match requester mentions.
Deployment modes
The plugin runs as a system role called Slack inside
almad. By default it is enabled on any process that also
runs the Auth Service, so a single-node cluster needs no extra knobs.
Apply a SlackConnector and the plugin is already listening for it.
You can disable the plugin on an auth process by passing
--no-slack-service on the command line or setting the
ALMA_SLACK_SERVICE=false environment variable (systemd drop-ins,
container envs, and Kubernetes env: all work). This is the
supported opt-out when you want the plugin configured at the cluster
level (the SlackConnector still exists) but not running on a
particular auth instance.
You can also run the plugin as a sidecar. This is a standalone
almad process that joins an existing cluster purely to
bridge Slack. It is the right shape when the auth cluster has no
egress to Slack's API and a separate bastion host talks to Slack on
its behalf.
On the auth cluster, mint a join token that grants the Slack
system role:
alma create token --services=slack --expires=1hOn the sidecar host, start almad with the token and the
Slack role. No configuration file is required. The auth address,
token, and role all come from flags or environment variables.
export ALMA_CLIENT_SECRET=/path/to/client-secretalmad start \ --auth-server=auth.example.com:443 \ --client-id=<client-id> \ --slack-serviceOr equivalently with environment variables:
export ALMA_AUTH_SERVER=auth.example.com:443export ALMA_CLIENT_ID=<client-id>export ALMA_CLIENT_SECRET=/path/to/client-secretexport ALMA_SLACK_SERVICE=truealmad startThe sidecar joins the cluster as system:slack, receives an identity
certificate, polls the SlackConnector/slack resource over the
standard auth gRPC channel, and runs the plugin locally.
Permissions and identity
The Slack integration runs under a built-in system role called
Slack. almad grants it the minimum set of permissions
it needs and nothing more: list and read on ClusterName, list and
readwithsecrets on SlackConnector so it can load its bot token,
read on CertificateAuthority, and list, read and update on
AccessRequest and AccessPluginData.
No hand-authored RBAC resources are required, and the role cannot
be used to sign in as a human.
Every Slack message is posted by the Slack app's bot user. In the
Slack audit log, every action taken by the integration will appear
as that bot, so name the Slack app something that makes the source
obvious. For example, AlmaForge Access Requests.
Removing the integration
Delete the connector resource:
alma delete SlackConnector/slackThe plugin stops on the next polling tick (within about 60 seconds)
without restarting almad. Existing Slack messages are left
in place untouched. The integration never deletes messages on its
own.