instructure/canvas-lms · error · ConfigError

kafka_events connection requires both sasl_username and…

Error message

kafka_events connection requires both sasl_username and sasl_password when SASL is in use

What it means

Canvas::KafkaEvents::Config#validate! ensures that if SASL authentication is configured (sasl_configured?), both sasl_username and sasl_password are present (sasl_credentials_complete?). A partial SASL config would produce a Kafka client that cannot authenticate, so ConfigError is raised at connection setup.

Solutions

  1. Set both sasl_username and sasl_password in the kafka_events DynamicSettings/config for the environment
  2. If SASL is not intended, remove the SASL mechanism/security-protocol keys so sasl_configured? is false
  3. Verify secret rotation kept both keys together
  4. Restart/re-validate config after fixing so validate! passes

Example fix

# before
config:
  sasl_mechanism: PLAIN
  sasl_username: canvas
// after
config:
  sasl_mechanism: PLAIN
  sasl_username: canvas
  sasl_password: <secret>
Defensive patterns

Strategy: validation

Validate before calling

raise 'SASL incomplete' if sasl_configured && (sasl_username.blank? || sasl_password.blank?)

Type guard

complete = cfg.is_a?(Hash) && cfg[:sasl_username].present? && cfg[:sasl_password].present?

Try / catch

begin
  config.validate!
rescue Canvas::KafkaEvents::ConfigError => e
  Rails.logger.error(e.message)
  raise
end

Prevention

When it happens

Trigger: Building/validating kafka_events config where SASL mechanism/security protocol is set but only one of sasl_username or sasl_password is provided in DynamicSettings or config files.

Common situations: Secrets manager missing one of the two SASL keys, config partially rotated, copy-paste of Kafka settings omitting the password, consul hierarchy overriding one key to empty.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/005e072f420a26f8. Report an issue: GitHub.

Appendix: source

Thrown at lib/canvas/kafka_events/config.rb:78

        "sasl.mechanisms": sasl_mechanism,
        "sasl.username": sasl_username,
        "sasl.password": sasl_password,
        "client.id": client_id,
        acks: "all",
        "enable.idempotence": "true",
        "compression.type": "zstd",
        "socket.keepalive.enable": "true",
        "linger.ms": "50",
        "message.timeout.ms": message_timeout_ms.to_s,
        "queue.buffering.max.messages": queue_buffering_max_messages.to_s,
        "reconnect.backoff.ms": reconnect_backoff_ms.to_s,
        "reconnect.backoff.max.ms": reconnect_backoff_max_ms.to_s,
      }.compact
    end

    def validate!
      if sasl_configured? && !sasl_credentials_complete?
        raise ConfigError,
              "kafka_events connection requires both sasl_username and sasl_password when SASL is in use"
      end

      missing = Events.topic_keys.reject { |key| topic_for(key).present? }
      return if missing.empty?

      raise ConfigError,
            "kafka_events has brokers configured but required topic(s) missing from DynamicSettings: #{missing.join(", ")}"
    end

    private

    def load_operational_config
      parsed = YAML.safe_load(DynamicSettings.find(tree: :private)["kafka_events.yml", failsafe: nil] || "{}")
      parsed.is_a?(Hash) ? parsed : {}
    rescue => e
      Rails.logger.warn("Kafka operational settings load error: #{e.message}")
      {}

View on GitHub (pinned to 1c9f0bb801)