Skip to main content

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.

One-way today

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 alma CLI pointed at it.
  • A requester allowed to request access and 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:write to post and update access-request messages.
    • users:read and users:read.email to resolve AlmaForge logins to Slack users for direct messaging and message attribution.
    • im:write to open direct-message conversations with reviewers and requesters when roleToRecipients routes 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 in roleToRecipients and forget to invite the bot, the plugin logs a not_in_channel error 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.

FieldRequiredDescription
spec.botTokenyesSlack 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.usernameClaimnoWhich 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.roleToRecipientsyesMap 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.

slack.yaml:

examples/integrations/slack.yaml
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:

Terminal
alma apply -f slack.yaml

The 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:

Terminal
journalctl -u almad -f | grep slack_service...  slack_service: connector found; starting plugin  recipient_roles=2...  Plugin is ready

If 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 in roleToRecipients. Invite it and wait for the next polling tick.
  • channel_not_found: a channel name in roleToRecipients does not exist in the workspace, or the bot cannot see it. Private channels need an explicit invite to become visible.
  • no such file or directory or permission denied: the file named by a ${file:PATH} reference in botToken does not exist or is not readable by root. A file over the 64 KiB cap is reported as exceeds 65536 bytes.

Now create a real access request and confirm that a Slack message appears in the target channel:

Terminal
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:

Terminal
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's users.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:

Terminal
alma create token --services=slack --expires=1h

On 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.

Terminal
export ALMA_CLIENT_SECRET=/path/to/client-secretalmad start \    --auth-server=auth.example.com:443 \    --client-id=<client-id> \    --slack-service

Or equivalently with environment variables:

Terminal
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 start

The 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:

Terminal
alma delete SlackConnector/slack

The 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.