Skip to content

AWS MSK IAM

SametGoktepe edited this page Jun 17, 2026 · 1 revision

AWS MSK exposes IAM auth via SASL/OAUTHBEARER — the bearer token is an AWS Signature V4 signature, not a JWT. @eventferry/kafka-iam wraps the official aws-msk-iam-sasl-signer-js library and gives you a one-liner sasl block for the publisher.

Separate package on purpose — the core @eventferry/kafka surface stays AWS-free, and users who don't run MSK never pull aws-sdk into their tree.


Install

npm i @eventferry/kafka-iam aws-msk-iam-sasl-signer-js

aws-msk-iam-sasl-signer-js is the AWS-official SigV4 signer; declared as an optional peer.


Minimal usage

import { KafkaPublisher } from "@eventferry/kafka";
import { createMskIamSasl } from "@eventferry/kafka-iam";

const publisher = new KafkaPublisher({
  brokers: ["b-1.cluster.us-east-1.amazonaws.com:9098"],
  ssl: true,
  sasl: createMskIamSasl({ region: "us-east-1" }),
});

await publisher.connect();

createMskIamSasl({ region }) returns a SaslOauthbearerConfig — drops directly into KafkaPublisher's sasl option.

The signer reads AWS credentials from the standard SDK chain: environment vars, EC2 instance metadata, ECS task role, EKS service account, ~/.aws/credentials. No explicit credential plumbing.


Named profile

createMskIamSasl({
  region: "us-east-1",
  awsProfile: "prod",                     // ~/.aws/credentials profile
});

Use when the deploy environment has multiple profiles or you're running locally against a non-default one.


Assumed role

createMskIamSasl({
  region: "us-east-1",
  awsRoleArn: "arn:aws:iam::123456789012:role/eventferry-publisher",
  awsRoleSessionName: "eventferry-prod",  // default "MSKSASLDefaultSession"
});

The signer calls AssumeRole itself before signing. Useful for cross-account MSK clusters where your service runs under a different account than the broker.

awsProfile and awsRoleArn are mutually exclusive — pick one.


VPC endpoint / FIPS regions

createMskIamSasl({
  region: "us-east-1",
  awsStsRegion: "us-east-1-fips",         // STS region override
});

Use when your STS endpoint differs from the signing region (FIPS / VPC endpoint setups).


Token caching

MSK IAM tokens have a 15-minute lifetime. The helper caches the most recent token process-locally and refreshes shortly before expiry:

  • Default refreshAheadMs: 60_000 — the cache returns a fresh token when remaining lifetime drops below 60 seconds.
  • Concurrent invocations during a refresh share a single in-flight signer call — no thundering herd at the SigV4 signer.
  • Transient signer failures (network blip during refresh) clear the in-flight slot so the next call retries cleanly. Otherwise a brief outage would poison the cache and break every subsequent publish.

Tighten or loosen the refresh window:

createMskIamSasl({
  region: "us-east-1",
  refreshAheadMs: 120_000,                // refresh 2 min before expiry
});

For workloads where you can't tolerate a single refresh blip, drop refreshAheadMs lower — the cost is more signer calls (~1 per refresh window vs ~1 every 14 minutes).


Custom principal name

The SASL principal defaults to "eventferry". Override when your alerting / audit expects a specific name:

createMskIamSasl({
  region: "us-east-1",
  principal: "orders-service",
});

This is the field that lands on the broker's principal audit log, not the IAM role name. The IAM identity is still determined by the SigV4 signature.


Testing — custom signer DI

For tests or non-standard signers:

createMskIamSasl({
  region: "us-east-1",
  signer: {
    async generateAuthToken({ region }) {
      return {
        token: await mySignerFor(region),
        expiryTime: Date.now() + 15 * 60 * 1000,
      };
    },
  },
});

When signer is set, the helper does NOT dynamically import aws-msk-iam-sasl-signer-js. Useful for unit tests that don't have AWS credentials available.


IAM permissions

The IAM identity running the publisher needs:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["kafka-cluster:Connect"],
      "Resource": "arn:aws:kafka:us-east-1:123456789012:cluster/your-cluster/<uuid>"
    },
    {
      "Effect": "Allow",
      "Action": ["kafka-cluster:WriteData", "kafka-cluster:DescribeTopic"],
      "Resource": "arn:aws:kafka:us-east-1:123456789012:topic/your-cluster/<uuid>/*"
    },
    {
      "Effect": "Allow",
      "Action": ["kafka-cluster:AlterCluster"],
      "Resource": "arn:aws:kafka:us-east-1:123456789012:cluster/your-cluster/<uuid>"
    }
  ]
}

If you use publisher.admin() / ensureTopics, add kafka-cluster:CreateTopic and kafka-cluster:DescribeCluster. If you use transactions, add kafka-cluster:WriteDataIdempotently.

For a complete policy by feature, see AWS MSK IAM docs.


Troubleshooting

Symptom Likely cause
createMskIamSasl requires the "aws-msk-iam-sasl-signer-js" package Optional peer not installed — npm i aws-msk-iam-sasl-signer-js.
Could not load credentials from any providers AWS credential chain didn't find anything. Set AWS_REGION / AWS_PROFILE, or attach an IAM role to the host.
Producer connects but immediate SASL_AUTHENTICATION_FAILED Token signed for wrong region, IAM identity missing Connect action, or cluster-level IAM auth not enabled.
Works locally, fails in EKS Service account → IAM role mapping (IRSA) missing. Annotate the SA with eks.amazonaws.com/role-arn.

What's next

Clone this wiki locally