Client certificate authentication - Mailing list pgsql-hackers
From | Magnus Hagander |
---|---|
Subject | Client certificate authentication |
Date | |
Msg-id | 491C1E2D.8060905@hagander.net Whole thread Raw |
Responses |
Re: Client certificate authentication
|
List | pgsql-hackers |
Attached patch implements client certificate authentication. I kept this sitting in my tree without sending it in before the commitfest because it is entirely dependent on the not-yet-reviewed-and-applied patch for how to configure client certificate requesting. But now that I learned how to do it right in git, breaking it out was very easy :-) Good learning experience. Anyway. Here it is. Builds on top of the "clientcert option for pg_hba" patch already on the list. //Magnus *** a/doc/src/sgml/client-auth.sgml --- b/doc/src/sgml/client-auth.sgml *************** *** 388,393 **** hostnossl <replaceable>database</replaceable> <replaceable>user</replaceable> --- 388,403 ---- </varlistentry> <varlistentry> + <term><literal>cert</></term> + <listitem> + <para> + Authenticate using SSL client certificates. See + <xref linkend="auth-cert"> for details. + </para> + </listitem> + </varlistentry> + + <varlistentry> <term><literal>pam</></term> <listitem> <para> *************** *** 1114,1119 **** ldapserver=ldap.example.net prefix="cn=" suffix="dc=example, dc=net" --- 1124,1148 ---- </sect2> + <sect2 id="auth-cert"> + <title>Certificate authentication</title> + + <indexterm zone="auth-cert"> + <primary>Certificate</primary> + </indexterm> + + <para> + This authentication method uses SSL client certificates to perform + authentication. It is therefore only available for SSL connections. + When using this authentication method, the server will require that + the client provide a certificate. No password prompt will be sent + to the client. The <literal>cn</literal> attribute of the certificate + will be matched with the username the user is trying to log in as, + and if they match the login will be allowed. Username mapping can be + used if the usernames don't match. + </para> + </sect2> + <sect2 id="auth-pam"> <title>PAM authentication</title> *** a/doc/src/sgml/runtime.sgml --- b/doc/src/sgml/runtime.sgml *************** *** 1674,1684 **** $ <userinput>kill -INT `head -1 /usr/local/pgsql/data/postmaster.pid`</userinput </para> <para> ! <productname>PostgreSQL</> currently does not support authentication ! using client certificates, since it cannot differentiate between ! different users. As long as the user holds any certificate issued ! by a trusted CA it will be accepted, regardless of what account the ! user is trying to connect with. </para> </sect2> --- 1674,1682 ---- </para> <para> ! You can use the authentication method <literal>cert</> to use the ! client certificate for authenticating users. See ! <xref linkend="auth-cert"> for details. </para> </sect2> *** a/src/backend/libpq/auth.c --- b/src/backend/libpq/auth.c *************** *** 110,115 **** ULONG(*__ldap_start_tls_sA) ( --- 110,123 ---- static int CheckLDAPAuth(Port *port); #endif /* USE_LDAP */ + /*---------------------------------------------------------------- + * Cert authentication + *---------------------------------------------------------------- + */ + #ifdef USE_SSL + static int CheckCertAuth(Port *port); + #endif + /*---------------------------------------------------------------- * Kerberos and GSSAPI GUCs *************** *** 420,425 **** ClientAuthentication(Port *port) --- 428,441 ---- #endif break; + case uaCert: + #ifdef USE_SSL + status = CheckCertAuth(port); + #else + Assert(false); + #endif + break; + case uaTrust: status = STATUS_OK; break; *************** *** 2072,2074 **** CheckLDAPAuth(Port *port) --- 2088,2115 ---- } #endif /* USE_LDAP */ + + /*---------------------------------------------------------------- + * SSL client certificate authentication + *---------------------------------------------------------------- + */ + #ifdef USE_SSL + static int + CheckCertAuth(Port *port) + { + Assert(port->ssl); + + /* Make sure we have received a username in the certificate */ + if (port->peer_cn == NULL || + strlen(port->peer_cn) <= 0) + { + ereport(LOG, + (errmsg("Certificate login failed for user \"%s\": client certificate contains no username", + port->user_name))); + return STATUS_ERROR; + } + + /* Just pass the certificate CN to the usermap check */ + return check_usermap(port->hba->usermap, port->user_name, port->peer_cn, false); + } + #endif *** a/src/backend/libpq/hba.c --- b/src/backend/libpq/hba.c *************** *** 859,864 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline) --- 859,870 ---- #else unsupauth = "ldap"; #endif + else if (strcmp(token, "cert") == 0) + #ifdef USE_SSL + parsedline->auth_method = uaCert; + #else + unsupauth = "cert"; + #endif else { ereport(LOG, *************** *** 893,898 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline) --- 899,915 ---- return false; } + if (parsedline->conntype != ctHostSSL && + parsedline->auth_method == uaCert) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("cert authentication is only supported on hostssl connections"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + return false; + } + /* Parse remaining arguments */ while ((line_item = lnext(line_item)) != NULL) { *************** *** 923,930 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline) if (parsedline->auth_method != uaIdent && parsedline->auth_method != uaKrb5 && parsedline->auth_method != uaGSS && ! parsedline->auth_method != uaSSPI) ! INVALID_AUTH_OPTION("map", "ident, krb5, gssapi and sspi"); parsedline->usermap = pstrdup(c); } else if (strcmp(token, "clientcert") == 0) --- 940,948 ---- if (parsedline->auth_method != uaIdent && parsedline->auth_method != uaKrb5 && parsedline->auth_method != uaGSS && ! parsedline->auth_method != uaSSPI && ! parsedline->auth_method != uaCert) ! INVALID_AUTH_OPTION("map", "ident, krb5, gssapi, sspi and cert"); parsedline->usermap = pstrdup(c); } else if (strcmp(token, "clientcert") == 0) *************** *** 957,963 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline) --- 975,992 ---- parsedline->clientcert = true; } else + { + if (parsedline->auth_method == uaCert) + { + ereport(LOG, + (errcode(ERRCODE_CONFIG_FILE_ERROR), + errmsg("clientcert can not be set to 0 when using \"cert\" authentication"), + errcontext("line %d of configuration file \"%s\"", + line_num, HbaFileName))); + return false; + } parsedline->clientcert = false; + } } else if (strcmp(token, "pamservice") == 0) { *************** *** 1021,1026 **** parse_hba_line(List *line, int line_num, HbaLine *parsedline) --- 1050,1063 ---- { MANDATORY_AUTH_ARG(parsedline->ldapserver, "ldapserver", "ldap"); } + + /* + * Enforce any parameters implied by other settings. + */ + if (parsedline->auth_method == uaCert) + { + parsedline->clientcert = true; + } return true; } *** a/src/backend/libpq/pg_hba.conf.sample --- b/src/backend/libpq/pg_hba.conf.sample *************** *** 35,41 **** # an IP address and netmask in separate columns to specify the set of hosts. # # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi", ! # "krb5", "ident", "pam" or "ldap". Note that "password" sends passwords # in clear text; "md5" is preferred since it sends encrypted passwords. # # OPTIONS are a set of options for the authentication in the format --- 35,41 ---- # an IP address and netmask in separate columns to specify the set of hosts. # # METHOD can be "trust", "reject", "md5", "crypt", "password", "gss", "sspi", ! # "krb5", "ident", "pam", "ldap" or "cert". Note that "password" sends passwords # in clear text; "md5" is preferred since it sends encrypted passwords. # # OPTIONS are a set of options for the authentication in the format *** a/src/include/libpq/hba.h --- b/src/include/libpq/hba.h *************** *** 26,32 **** typedef enum UserAuth uaGSS, uaSSPI, uaPAM, ! uaLDAP } UserAuth; typedef enum ConnType --- 26,33 ---- uaGSS, uaSSPI, uaPAM, ! uaLDAP, ! uaCert } UserAuth; typedef enum ConnType
pgsql-hackers by date: