Permission denied error

  • KumoMTA latest build as of 31st Oct 2023
  • OS is Ubuntu 22.04

Hi guys, When I configure HTTP listener or SMTP listener with TLS certs from LetsEncrypt - it gives me an error. Here’s my HTTP listener config

kumo.start_http_listener {
    use_tls = true,
    listen = '0.0.0.0:8001',
    hostname = 'xxx.com',
    -- allowed to access any http endpoint without additional auth
    trusted_hosts = { '127.0.0.1', '::1' },
    tls_certificate = '/home/kumomta/ssl/cert.pem',
    tls_private_key = '/home/kumomta/ssl/key.pem',
  }

The tls_certificate and tls_private_key has chown by kumod and I am running using the following command:
sudo KUMOD_LOG=kumod=debug /opt/kumomta/sbin/kumod --policy /opt/kumomta/etc/policy/init.lua --user kumod

The error I am getting is:

2023-10-30T22:08:46.109744Z DEBUG     logger kumod::logging: waiting until deadline=None for a log record
2023-10-30T22:08:46.111633Z DEBUG       main kumod::logging: Terminating a logger
2023-10-30T22:08:46.111661Z DEBUG       main kumod::logging: Joining that logger
2023-10-30T22:08:46.111679Z DEBUG     logger kumod::logging: LogCommand::Terminate received. Stopping writing logs
2023-10-30T22:08:46.111804Z DEBUG     logger kumod::logging: Clearing any buffered files prior to completion
2023-10-30T22:08:46.112028Z DEBUG       main kumod::logging: Joined -> Some(Ok(()))
Error: Initialization raised an error: call init callback: callback error
stack traceback:
        [C]: in local 'poll'
        [string "?"]:5: in function 'kumo.start_http_listener'
        [string "/opt/kumomta/etc/policy/init.lua"]:54: in function <[string "/opt/kumomta/etc/policy/init.lua"]:13>
caused by: Permission denied (os error 13)

The permissions of the SSL files are:

-rw-r--r--  1 kumod   kumod   1521 Oct 30 21:21 cert.pem
-rw-------  1 kumod   kumod    241 Oct 30 21:21 key.pem

Any help would be much appreciated. Thanks

/home/kumomta/ssl/cert.pem (and key.pem) and its containing director(ies) need to be readable to the kumod user

And also the error disappears the minute I remove the tls_certificate and tls_private_key paths from the init.lua config.

Ok thanks Wez. let me try changing permissions on the files. It is currently:

-rw-r--r--  1 kumod   kumod   1521 Oct 30 21:21 cert.pem
-rw-------  1 kumod   kumod    241 Oct 30 21:21 key.pem

you should be able to reproduce that error more conveniently by doing something like sudo -u kumod ls /home/kumomta/ssl/cert.pem

Yea much better - let me tinker - thanks mate

@free-spirited-yorksh I was wondering if we could set SMTP authentication using JSON file containing the username and password?

Btw to anyone who is struggling with similar error, I set the permissions to CHMOD 600 and put it in an accessible place to kumod user in /home directory and chown it to kumod user. That resolved the issue there. I used a little script to copy files from Let’s Encrypt default location before I did this.

I am worried to use local SQLite DB because we would need to import new/changed records every 5 minutes and kumod user would probably be accessing the same DB file which is bad practice right? So thinking maybe we could achieve similar result with JSON file. We will use our default MySQL DB to export credentials to a JSON file every 5 minutes and let KumoMTA use that for doing AUTH checks for both SMTP and HTTP.

You could use either sqlite (provided that you use sqlite itself to update the db: it has transactions and supports multiple reader/writer processes) or a json file. You can use memoize - KumoMTA Docs to cache the lookups from either of those

Thanks for that, wez! Sorry just another question in relation to it. I’ve setup the SQLite with username and password credentials. Here’s my lua config. I am just not sure where to place in the init.lua file. Does it go inside the SMTP listener?

-- Custom authentication START
  function sqlite_auth_check(user, password)
  local db = sqlite.open '/home/simple.db'
  local result = db:execute(
    'select * from customer where email=? and apikey=?',
    user,
    password
  )
  -- if we got any rows, it was because a user+pass matched
  return #result == 1
end

-- This creates a new function called `cached_sqlite_auth_check`
-- that remembers the results for a given set of parameters for up
-- to 5 minutes or up to 100 different sets of parameters
cached_sqlite_auth_check = kumo.memoize(sqlite_auth_check, {
  name = 'sqlite_auth',
  ttl = '5 minutes',
  capacity = 100,
})

kumo.on('smtp_server_auth_plain', function(authz, authc, password)
  return cached_sqlite_auth_check(authc, password)
end)

-- Custom authentication END

Ideally we want it to enforce AUTH checks under every situation for both HTTP and SMTP listeners since we opened relay to all using the following listener_domains.toml file

["*"]
# You can specify * as a default, overridden by any more explicitly defined domains.
# Since all options are false by default, this would only be needed to default
# An option to true for all domains.
relay_from_authz = true
log_oob = false
log_arf = false

Not sure if this is how you would do it or not.

Auth checks are done in smtp_server_auth_plain - KumoMTA Docs for SMTP

and http_server_validate_auth_basic - KumoMTA Docs for HTTP
in both cases, username and password is verified in these events and the true/false status is passed back to the smtp_server_mesage_received or http_message_generated event respectively.

so in your case, I would run the DB lookup in those, or load a table on startup and cache it in memory, depending on how many users there are.

for your TLS question, I usually place TLS certs in /opt/kumomta/etc/tls/.ca | .crt

and chmod 600. Here, they are available to kumod and easy to work with.

If it helps, this is from my own demo system authentication which uses basic auth for both SMTP and HTTP:

--[[ Adding basic Authentication (on disk) ]]--
-------------------------------------

-- FOR HTTP --
-- Use this to lookup and confirm a user/password
-- used with the http endpoint
kumo.on('http_server_validate_auth_basic', function(user, password)
 
  local password_database = {
    ['myusername'] = 'This_is_a_Bad_Password',
  }
  if password == '' then
    return false
  end
  return password_database[user] == password
end)


-- FOR SMTP --
-- Use this to lookup and confirm a user/password 
-- used with the smtp endpoint
kumo.on('smtp_server_auth_plain', function(authz, authc, password)
  local password_database = {
    ['myusername'] = 'This_is_a_Bad_Password',
  }

  if password == '' then
    return false
  end
  return password_database[authc] == password
end)
-----------------------

You don’t have to “call” these at all. If the the inbound message includes credentials, they will automatically be checked in these events.

This means that your received message code can be very clean. here is the HTTP example from my demo system:

  local tenant = msg:get_first_named_header_value('x-tenant') or 'default'
  msg:set_meta('tenant',tenant)
  msg:remove_x_headers { 'x-tenant' }

-- SIGNING MUST COME LAST OR YOU COULD BREAK YOUR DKIM SIGNATURES
  dkim_signer(msg)
end)

^^ You dont even need all that, I just like to use the tenant meta value to bucket “customers” or message types.

Notice there is no visible validation check. If the validation returns false, then the message is bounced transparently to your config.

`For the auth_z section you have in listener_domains, that should work fine as it is (sort of). That is not really an “open_relay”, what it does is allow relay for any domain as long as the username and password are found in your DB. The only problem I see is that you don’t have it tied to a domain so user@password on domain x.com will also be able to send mail for y.com and abc.com as long as the credentials exist. I would highly recommend adding a section for each sending domain and add the specific users under each to make sure there is no bleeding of access.