in Security, Sysadmin

Scalable access control using OpenSSH Certificates


I’ve been using OpenSSH certificates for some time now. They are very handy if you have a bunch of machines you want to trust, or a bunch of machines that shoud trust you.  It’s very effective to trust just one host CA in order to trust all servers with certificates signed by this CA. Or the other way around, have your personal public key signed by a user CA and then be automatically trusted by all servers that trust this CA. But if you working together with alot of people let say within an organisation this becomes problematic pretty soon. Maybe your frontend people should only have access to webservers and database people to the database servers and so on. The solution to this is the little known flags AuthorizedPrincipalsFile and AuthorizedPrincipalsCommand in sshd_config.

OpenSSH Certificates and principals

Lets start with the certificates and the principals within these certificates. In order to make this work I would suggest to use principals within the certificates that are closely tied to the person using it, their company wide username for example. If different keys have different access leves (lets say because on of them are stored on a physical secure element/smart card) it is good to include this kind of information in the principal, according to some standard you make up. Let say I use yubikeys to store my private keys I could have principals like peter_file and peter_physcial. These are easily parsable and  connected to a physical person.

Please note that there could be other access schemes where role is more important that how the key is stored, then role could be a better option for the principal suffix. But please note that if you burn the role into the certificate that person will need to have the current certificate revoked and have a new one issued if the role is ever changed. I will discuss a better way to handle this later.

Another option is to have multiple principals in the same certificate (ie peter,webmaster,root) but this also gets cumbersome when privileges and roles start to change over time.


One solution to this problem is the configuration option AuthorizedPrincipalsFile in sshd_config. With this option you tell sshd where to look for a list of principals valid for a certain user. I looks something like

AuthorizedPrincipalsFile /etc/ssh/%u_principals

When someone tries to log in as peter sshd will check my certificate for validity and then look for valid principals in the file /etc/ssh/peter_principals. sshd expects this file to contain one valid principal per line and optionally preceded by extra options using the same format as the authorized_keys file. (ie from= and command=). This is flexible enough. I can now give multiple principals (or physical persons) access to a specific account by changing a file. I can also restrict access to certain hosts or create force commands for specific principals.

One use case for this could be a webserver where multiple principals(persons) should be able to use the “webmaster” account but at the same time the test/build system should only be allowed to run a certain commands to publish successful builds. Lets say that the account name is www, then the /etc/ssh/www_principals could look something like this:

peter erik from="" command="/bin/publish_website"
buildserver from="" guestworker

Please note: If you are really concerned with security, maybe you have given a certificate to a external partner or something, I would suggest to burn the from and command attributes into the certificate. In this way they will never be overridden by some configuration at the server side. The downside is that you will need to produce a new certificate if something changes.

If you have a pretty static setup and/or a decent configuration manager/orchestration tool this could be enough. It gives full flexibility on who should be able to access what, and how. But in the long run it could be tedious to manage all the principal files. This is where the AuthorizedPrincipalsCommand comes in to the picture.


This works exactly the same as AuthorizedPrincipalsFile but instead of a static file sshd will run a command followed by some options (basically the username that tries to log in) that will generate the principals file dynamically. This gives you a lot of options. Probably the most straight forward one is that you now can have a single ACL file for you whole environment and just let the command read it and produce a host and user specific principal file. One very simple example could look like this:

# Principal(person) user     host
erik                www
peter               www
guestworker         www

peter               db
erik                db

Of course this file could be expanded to include more information and more options but this gives a example on how it can be done.

Other backends

But I think the real power in the AuthorizedPrincipalsCommand is that you now can use whatever backend you like and just have the principals command be a wrapper for this backend which could be some Active Directory, LDAP or whatever you might have at your organisation. This makes it possible to use existing infrastructure and still be able to use ssh certificates which I think is a real killer feature in OpenSSH.

If you have some ideas to improve this concept or an questions, please leave a comment.

Write a Comment


  1. Cool. I just found out about SSH CAs recently and I was wonering: hey wouldn’t this mean any signed user will have access to all accounts that trust the CA?

    Apparently, it doesn’t have to be that way! Thanks for the tips.

  2. Hello,

    I’m trying to setup force command for root user with authorized_principals file, because I’m using ssh CA in my environemnt. My attempt result with an error “Certificate does not contain an authorized principal”, which is not the case and can be seen that principal is the same in authorized_principals file and ssh user certificate –

    It’s interesting that the same setup work if authorized_keys file is used isntead and it has similar syntax as authorized_principals file.

    My conclusion is that if TrustedUserCAKeys is defined in sshd_config, sshd service will firstly check authorized_principals file for principal match and only if there is no mach it will proceed to authorized_keys.

    Please check authorized_principals file and let me know if I’m doing something wrong or have syntax error, because authorized_principals format is not precisely defined on the Internet.

    authorized_principals file in root user home on remote seerver:

    authorized_keys file in root user home on remote seerver:
    cert-authority,command=”/sbin/ifconfig”,no-agent-forwarding,no-port-forwarding,no-pty,no-X11-forwarding,principals=”” ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQDDFJU7IPlVkSWvMTFVEEaWAuF/6VehtVBEmr0Q4ihqqfnx/JCOzCmhrIcJ+pR+ifOMMriL/6yd7nMQXI8ZFG4j/mTAzbTbV1qK1rhhF5Cjn/Y+zmk47rDD8zANcgdzJ6KyVzjGo6hXdcLuH80QC+OUAttZF0fCNil+48LclsA6tdtCXcl8gkOytbHKYLsT8cYvVdp6vNqBpn7EN7CupQ7+cjfb3iaGOn6LopHwPHA/ccCGTccsRMdqryE085seyoNmGM6bd8K2vkWWzQb/QNQ9NqK+vpzZWKtaY48/F80RAndUQiY5t6am74VLjJrN9VRpKaa1fP+lHahM7nOIi5BT/q2wnKzMzErMAfypy1jGq5PupusZ+CLYHc/qzHm09HCGYYYH6nM0CxhDo1MsUtcyZMDgEr4X8Q41ygdlL+8XIAzY7Oqt18dZPPIvY1mWfiE5z2OdLsyOI7hjsOSoCkLN65rr+z39Una+E66g9GbkTlaFoRXnoEbiuIpASQQY40v2Vz+k0tMqFxaI1VMsxvpg2zOfpRtrRUpOuq2tmNqkOhaHrM6RU4lyV5sgzZLCS1zTdK/9764jTkLYorP3/8GAFSn950sP7bjzWH0ONcWA5kauoyK86BN0ncW/njanxReY0pUbpbtCNo8hjZGH3ZwIfLf4/l5hDUdosCQgd8nnCQ== User Signing CA

    ssh user certificate:
    Type: user certificate
    Public key: RSA-CERT SHA256:61zNSnUJ/2gyjo838P0U8H8eqQR1EkhJPj7pF9CaxUU
    Signing CA: RSA SHA256:AsEE0T/P7Z0o/s6q8egBquay8WLL2sJHOLzYfc3N484
    Key ID: “user-0001”
    Serial: 0
    Valid: from 2018-07-31T11:54:00 to 2020-07-30T11:55:56
    Critical Options: (none)

    related lines from sshd_config on remote server:

    TrustedUserCAKeys /etc/ssh/
    PermitRootLogin forced-commands-only
    AuthorizedKeysFile %h/.ssh/authorized_keys
    AuthorizedPrincipalsFile %h/.ssh/authorized_principals

    • Hi, The frist thing that comes to mind is that “” is a pretty weird *nix-username. Could this be a problem?

      My other question, how is authorized_keys involved in this?

      • Hello,

        You may put anything as principle name and it’s only important to mach in both certificate and authorized_principals or authorized_keys, depending what you use. I have also tried to change the principal to “user” and it still throws the same error – “Certificate does not contain an authorized principal”. I do not need the authorized_keys file, because authorized_principals should be sufficient. As I have mentioned sshd service finds TrustedUserCAKeys definition in sshd_config file and therefore firstly chech authorized_principals for principal match in certificate. Because of mentioned errot, principal is not detected in authorized_principals file and therefore proceeds checking authorized_keys file where it finds the match. In my config authorized_keys file should not be present and therefore ssh connection will not be established. This can be seen in logs.

        TrustedUserCAKeys /etc/ssh/
        PermitRootLogin forced-commands-only
        AuthorizedKeysFile %h/.ssh/authorized_keys
        AuthorizedPrincipalsFile %h/.ssh/authorized_principals

        You may see what I have described in sshd service log:

        debug1: userauth-request for user root service ssh-connection method none
        debug1: attempt 0 failures 0
        debug3: mm_getpwnamallow entering
        debug3: mm_request_send entering: type 7
        debug3: mm_getpwnamallow: waiting for MONITOR_ANS_PWNAM
        debug3: mm_request_receive_expect entering: type 8
        debug3: mm_request_receive entering
        debug3: monitor_read: checking request 7
        debug3: mm_answer_pwnamallow
        debug2: parse_server_config: config reprocess config len 898
        debug3: mm_answer_pwnamallow: sending MONITOR_ANS_PWNAM: 1
        debug3: mm_request_send entering: type 8
        debug2: monitor_read: 7 used once, disabling now
        debug3: mm_request_receive entering
        debug2: input_userauth_request: setting up authctxt for root
        debug3: mm_start_pam entering
        debug3: mm_request_send entering: type 50
        debug3: mm_inform_authserv entering
        debug3: mm_request_send entering: type 3
        debug3: mm_inform_authrole entering
        debug3: mm_request_send entering: type 4
        debug2: input_userauth_request: try method none
        debug3: Wrote 56 bytes for a total of 3597
        debug3: monitor_read: checking request 50
        debug1: PAM: initializing for “root”
        debug1: PAM: setting PAM_RHOST to “X.X.X.X”
        debug1: PAM: setting PAM_TTY to “ssh”
        debug2: monitor_read: 50 used once, disabling now
        debug3: mm_request_receive entering
        debug3: monitor_read: checking request 3
        debug3: mm_answer_authserv: service=ssh-connection, style=
        debug2: monitor_read: 3 used once, disabling now
        debug3: mm_request_receive entering
        debug3: monitor_read: checking request 4
        debug3: mm_answer_authrole: role=
        debug2: monitor_read: 4 used once, disabling now
        debug3: mm_request_receive entering
        debug1: userauth-request for user root service ssh-connection method publickey
        debug1: attempt 1 failures 0
        debug2: input_userauth_request: try method publickey
        debug1: ssh_rsa_verify: signature correct
        debug1: test whether pkalg/pkblob are acceptable
        debug3: mm_key_allowed entering
        debug3: mm_request_send entering: type 21
        debug3: mm_key_allowed: waiting for MONITOR_ANS_KEYALLOWED
        debug3: mm_request_receive_expect entering: type 22
        debug3: mm_request_receive entering
        debug3: monitor_read: checking request 21
        debug3: mm_answer_keyallowed entering
        debug1: ssh_rsa_verify: signature correct
        debug3: mm_answer_keyallowed: key_from_blob: 0x7fb71cddd940
        debug1: temporarily_use_uid: 0/0 (e=0/0)
        debug1: trying authorized principals file /root/.ssh/authorized_principals
        debug1: fd 4 clearing O_NONBLOCK
        debug3: secure_filename: checking ‘/root/.ssh’
        debug3: secure_filename: checking ‘/root’
        debug3: secure_filename: terminating check at ‘/root’
        debug1: restore_uid: 0/0
        Certificate does not contain an authorized principal
        debug1: temporarily_use_uid: 0/0 (e=0/0)
        debug1: trying public key file /root/.ssh/authorized_keys
        debug1: Could not open authorized keys ‘/root/.ssh/authorized_keys’: No such file or directory
        debug1: restore_uid: 0/0
        debug1: temporarily_use_uid: 0/0 (e=0/0)
        debug1: trying public key file /root/.ssh/authorized_keys
        debug1: Could not open authorized keys ‘/root/.ssh/authorized_keys’: No such file or directory
        debug1: restore_uid: 0/0
        Failed publickey for root from X.X.X.X port 49780 ssh2
        debug3: mm_answer_keyallowed: key 0x7fb71cddd940 is not allowed
        debug3: mm_request_send entering: type 22
        debug3: mm_request_receive entering
        debug2: userauth_pubkey: authenticated 0 pkalg
        debug3: Wrote 56 bytes for a total of 3653

        • Hi again. Yes you are of course correct. I answered too quickly. I even mention that in this post.. 🙂

          Anyways, I cant really see anything wrong at first glance, but traveling at the moment so I will not have any time to do any testing. One guess could be PermitRootLogin. Have you tried leaving that out temporarily?

  3. Yes, I have tried reverting PermitRootLogin to any other value and it behaves the same. It actualy makes no difference for any other user if the same authorized_principals file is present in its home. Because AuthorizedPrincipalsFile and AuthorizedKeysFile are defined in sshd_config, match with certificate principal must happen or connection will be refused.

    If AuthorizedPrincipalsFile and AuthorizedKeysFile are not used (defined to none in sshd_config) and TrustedUserCAKeys are defined, user certificate principal must mach the username on remote machine (default behaviour) in order to establish connection. Alternative is custom principal in user certificate and existence of AuthorizedKeysFile with same principal defined (e.g. principals=””). This is actually the answer to question from first comment, that was asked by dw.