Skip to content

Unexpected checking of principals #15

@candlerb

Description

@candlerb

I am testing pam-ussh. I am logging in to the target machine as user "web", and my certificate has principals ["web@anywhere" "database@anywhere" "root@anywhere"]. This works for ssh login because my sshd_config has:

TrustedUserCAKeys /etc/ssh/trusted_user_ca
AuthorizedPrincipalsCommand /etc/ssh/authprinc.sh %u
AuthorizedPrincipalsCommandUser nobody

and the script /etc/ssh/authprinc.sh is

#!/bin/sh
echo "$1@webserver"
echo "$1@anywhere"

This gives me a way of authorizing access to machines in groups, similar to zones described here.

So far so good. However, pam-ussh refuses to permit sudo. I have configured it with default options:

auth [success=1 default=ignore] pam_ussh.so
auth requisite                  pam_deny.so
auth required                   pam_permit.so

Note in particular that I have not specified authorized_principals or authorized_principals_file, and the documentation says they both default to ""

Initially, pam-ussh logs only "no valid certs found". So I made a small patch to log some more info:

--- a/pam_ussh.go
+++ b/pam_ussh.go
@@ -133,6 +133,11 @@ func authenticate(w io.Writer, uid int, username, ca string, principals map[stri
                in = rest
        }

+       if len(caPubkeys) == 0 {
+               pamLog("No certificate authority keys available.")
+               return AuthError
+       }
+
        c := &ssh.CertChecker{
                IsUserAuthority: func(auth ssh.PublicKey) bool {
                        for _, k := range caPubkeys {
@@ -147,15 +152,18 @@ func authenticate(w io.Writer, uid int, username, ca string, principals map[stri
        for idx := range keys {
                pubKey, err := ssh.ParsePublicKey(keys[idx].Marshal())
                if err != nil {
+                       pamLog("pubkey %d could not be parsed: %v\n", idx, err)
                        continue
                }

                cert, ok := pubKey.(*ssh.Certificate)
                if !ok {
+                       pamLog("pubkey %d is not a certificate\n", idx)
                        continue
                }

                if err := c.CheckCert(username, cert); err != nil {
+                       pamLog("pubkey %d fails CheckCert: %v\n", idx, err)
                        continue
                }

Now what I get is this:

May  2 13:31:55 sagan pam-ussh[2355]: pubkey 0 is not a certificate
May  2 13:31:55 sagan pam-ussh[2355]: pubkey 1 is not a certificate
May  2 13:31:55 sagan pam-ussh[2355]: pubkey 2 is not a certificate
May  2 13:31:55 sagan pam-ussh[2355]: pubkey 3 fails CheckCert: ssh: principal "web" not in the set of valid principals for given certificate: ["web@anywhere" "database@anywhere" "root@anywhere"]
May  2 13:31:55 sagan pam-ussh[2355]: no valid certs found

(My ssh agent has three private keys and one cert)

It appears that ssh.CertChecker.CheckCert is rejecting the certificate on the basis that it requires a particular principal, equal to the logged-in username. Digging further, it's the "username" argument being passed into CheckCert, which is checked against the list of principals in the certificate, if this list is non-empty. This is taking place before pam-ussh does its own principal checks.

As a result, there seems to be a hard-coded policy that if the certificate contains principals, then the bare ssh login name must exist as a principal in that certificate - which unfortunately doesn't work with my scheme for principals.

As far as I can see, this restriction is in the go ssh library, and there's not much that pam-ussh can do about it short of re-implementing the CheckCert function. I could propose a change to the go library here so that if the principal you pass in is empty string, this check is skipped:

	if len(principal) > 0 and len(cert.ValidPrincipals) > 0 {

A better option might be to have a setting for pam-ussh to pass a fixed string, e.g. "sudo" to CheckCert. This would then accept any certificate which includes that principal.

However, assuming that neither of the above happens, I still think it would be worthwhile to:

  1. Document this limitation, i.e. that principals are checked even if you don't configure authorized_principals or authorized_principals_file
  2. Add a "debug" flag to pam-ussh so that information like that shown above can be printed, to help diagnose such problems

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions