{"record":{"id":"005e072f420a26f8","repo":"instructure/canvas-lms","slug":"kafka-events-connection-requires-both-sasl-username-and-sasl","errorCode":null,"errorMessage":"kafka_events connection requires both sasl_username and sasl_password when SASL is in use","messagePattern":"kafka_events connection requires both sasl_username and sasl_password when SASL is in use","errorType":"validation","errorClass":"ConfigError","httpStatus":null,"severity":"error","filePath":"lib/canvas/kafka_events/config.rb","lineNumber":78,"sourceCode":"        \"sasl.mechanisms\": sasl_mechanism,\n        \"sasl.username\": sasl_username,\n        \"sasl.password\": sasl_password,\n        \"client.id\": client_id,\n        acks: \"all\",\n        \"enable.idempotence\": \"true\",\n        \"compression.type\": \"zstd\",\n        \"socket.keepalive.enable\": \"true\",\n        \"linger.ms\": \"50\",\n        \"message.timeout.ms\": message_timeout_ms.to_s,\n        \"queue.buffering.max.messages\": queue_buffering_max_messages.to_s,\n        \"reconnect.backoff.ms\": reconnect_backoff_ms.to_s,\n        \"reconnect.backoff.max.ms\": reconnect_backoff_max_ms.to_s,\n      }.compact\n    end\n\n    def validate!\n      if sasl_configured? && !sasl_credentials_complete?\n        raise ConfigError,\n              \"kafka_events connection requires both sasl_username and sasl_password when SASL is in use\"\n      end\n\n      missing = Events.topic_keys.reject { |key| topic_for(key).present? }\n      return if missing.empty?\n\n      raise ConfigError,\n            \"kafka_events has brokers configured but required topic(s) missing from DynamicSettings: #{missing.join(\", \")}\"\n    end\n\n    private\n\n    def load_operational_config\n      parsed = YAML.safe_load(DynamicSettings.find(tree: :private)[\"kafka_events.yml\", failsafe: nil] || \"{}\")\n      parsed.is_a?(Hash) ? parsed : {}\n    rescue => e\n      Rails.logger.warn(\"Kafka operational settings load error: #{e.message}\")\n      {}","sourceCodeStart":60,"sourceCodeEnd":96,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/canvas/kafka_events/config.rb#L60-L96","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set both sasl_username and sasl_password in the kafka_events DynamicSettings/config for the environment","If SASL is not intended, remove the SASL mechanism/security-protocol keys so sasl_configured? is false","Verify secret rotation kept both keys together","Restart/re-validate config after fixing so validate! passes"],"exampleFix":"# before\nconfig:\n  sasl_mechanism: PLAIN\n  sasl_username: canvas\n// after\nconfig:\n  sasl_mechanism: PLAIN\n  sasl_username: canvas\n  sasl_password: <secret>","handlingStrategy":"validation","validationCode":"raise 'SASL incomplete' if sasl_configured && (sasl_username.blank? || sasl_password.blank?)","typeGuard":"complete = cfg.is_a?(Hash) && cfg[:sasl_username].present? && cfg[:sasl_password].present?","tryCatchPattern":"begin\n  config.validate!\nrescue Canvas::KafkaEvents::ConfigError => e\n  Rails.logger.error(e.message)\n  raise\nend","preventionTips":["Store both SASL secrets together as one secret bundle","Validate kafka_events config at deploy/boot time","Diff DynamicSettings trees between environments before rollout"],"tags":["kafka","sasl","configuration","authentication"],"backgroundTag":"missing-credentials","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}