From: Muhammad Usama Date: Wed, 25 Apr 2018 20:24:16 +0000 (+0500) Subject: More for SCRAM authentication support, This commit also adds the support for X-Git-Url: http://git.postgresql.org/gitweb/static/gitweb.js?a=commitdiff_plain;h=ed07e4fd6f6c38f948489f558f3f70849ff68442;p=pgpool2.git More for SCRAM authentication support, This commit also adds the support for CERT authentication between client and Pgpool-II. The authentication infrastructure of Pgpool-II is also enhanced by this commit and now it is possible to use different authentication methods between client to Pgpool-II and Pgpool-II to backend. --- diff --git a/src/auth/pool_auth.c b/src/auth/pool_auth.c index b187eff6f..ae5af2f39 100644 --- a/src/auth/pool_auth.c +++ b/src/auth/pool_auth.c @@ -30,6 +30,7 @@ #include "utils/elog.h" #include "utils/palloc.h" #include "utils/memutils.h" +#include "auth/md5.h" #ifdef HAVE_SYS_TYPES_H #include #endif @@ -53,7 +54,9 @@ static POOL_STATUS pool_send_backend_key_data(POOL_CONNECTION *frontend, int pid static int do_clear_text_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); static void pool_send_auth_fail(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp); static int do_crypt(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); -static int do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); +static int do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor, + char *storedPassword, PasswordType passwordType); +static int do_md5_single_backend(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); static void send_md5auth_request(POOL_CONNECTION *frontend, int protoMajor, char *salt); static int read_password_packet(POOL_CONNECTION *frontend, int protoMajor, char *password, int *pwdSize); static int send_password_packet(POOL_CONNECTION *backend, int protoMajor, char *password); @@ -61,10 +64,16 @@ static int send_auth_ok(POOL_CONNECTION *frontend, int protoMajor); static void sendAuthRequest(POOL_CONNECTION *frontend, int protoMajor, int32 auth_req_type, char *extradata, int extralen); static long PostmasterRandom(void); -static int doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor); static int pg_SASL_continue(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, char *payload, int payloadlen, void *sasl_state, bool final); -static void* pg_SASL_init(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, char *payload, int payloadlen); -static bool doSCRAMBackendAuth(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, int protoMajor, int message_length); +static void* pg_SASL_init(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, char *payload, int payloadlen, char* storedPassword); +static bool do_SCRAM(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, int protoMajor, int message_length, + char *storedPassword, PasswordType passwordType); +static void authenticate_frontend_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor); +static void authenticate_frontend_cert(POOL_CONNECTION *frontend); +static void authenticate_frontend_SCRAM(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth); +static void authenticate_frontend_clear_text(POOL_CONNECTION *frontend); +static bool get_auth_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, + char** password, PasswordType *passwordType); /* * After sending the start up packet to the backend, do the @@ -155,7 +164,6 @@ int pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) ereport(DEBUG1, (errmsg("authentication backend"), errdetail("auth kind:%d", authkind))); - /* trust? */ if (authkind == AUTH_REQ_OK) { @@ -171,7 +179,7 @@ int pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) msglen = htonl(0); pool_write_and_flush(frontend, &msglen, sizeof(msglen)); - MASTER(cp)->auth_kind = 0; + MASTER(cp)->auth_kind = AUTH_REQ_OK; } /* clear text password authentication? */ @@ -228,20 +236,45 @@ int pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp) /* md5 authentication? */ else if (authkind == AUTH_REQ_MD5) { - /* If MD5 auth is not active in pool_hba.conf, it cannot be - * used with other than raw mode. + char *password; + PasswordType passwordType; + + /* + * check if we can use md5 authentication. */ - if ((frontend->pool_hba == NULL || frontend->pool_hba->auth_method != uaMD5) && !RAW_MODE && NUM_BACKENDS > 1) + if (NUM_BACKENDS > 1) { - pool_send_error_message(frontend, protoMajor, AUTHFAIL_ERRORCODE, - "MD5 authentication is unsupported in replication and master-slave modes.", - "", - "check pg_hba.conf", - __FILE__, __LINE__); - ereport(ERROR, - (errmsg("failed to authenticate with backend"), - errdetail("MD5 authentication is not supported in replication and master-slave modes."), - errhint("check pg_hba.conf settings on backend node"))); + if (get_auth_password(MASTER(cp), frontend, 0, + &password, &passwordType) == false) + { + /* + * We do not have any passeord, + * we can still get the password from client using plain text authentication + * if it is allowed by user + */ + if (frontend->pool_hba == NULL /*&& config allows */) + { + ereport(LOG, + (errmsg("usign clear text authentication with frontend"), + errdetail("backend will still use md5 auth"))); + authenticate_frontend_clear_text(frontend); + /* now check again if we have a password now */ + if (get_auth_password(MASTER(cp), frontend, 0, + &password, &passwordType) == false) + { + ereport(ERROR, + (errmsg("failed to authenticate with backend using md5"), + errdetail("unable to get the password"))); + } + } + } + /* we have a password to use, validate the password type */ + if (passwordType != PASSWORD_TYPE_PLAINTEXT && passwordType != PASSWORD_TYPE_MD5) + { + ereport(ERROR, + (errmsg("failed to authenticate with backend using md5"), + errdetail("password type is not valid"))); + } } for (i=0;ipool_hba == NULL || frontend->pool_hba->auth_method != uaSCRAM) + char *password; + PasswordType passwordType; + + if (get_auth_password(MASTER(cp), frontend, 0, + &password, &passwordType) == false) + { + /* + * We do not have any passeord, + * we can still get the password from client using plain text authentication + * if it is allowed by user + */ + if (frontend->pool_hba == NULL /*&& config allows */) + { + ereport(LOG, + (errmsg("usign clear text authentication with frontend"), + errdetail("backend will still use SCRAM auth"))); + authenticate_frontend_clear_text(frontend); + /* now check again if we have a password now */ + if (get_auth_password(MASTER(cp), frontend, 0, + &password, &passwordType) == false) + { + ereport(ERROR, + (errmsg("failed to authenticate with backend using SCRAM"), + errdetail("unable to get the password"))); + } + } + } + /* we have a password to use, validate the password type */ + if (passwordType != PASSWORD_TYPE_PLAINTEXT) { - pool_send_error_message(frontend, protoMajor, AUTHFAIL_ERRORCODE, - "SCRAM authentication is unsupported in replication and master-slave modes.", - "", - "check pg_hba.conf", - __FILE__, __LINE__); ereport(ERROR, - (errmsg("failed to authenticate with backend"), - errdetail("SCRAM authentication is not supported in replication and master-slave modes."), - errhint("check pg_hba.conf settings on backend node"))); + (errmsg("failed to authenticate with backend using SCRAM"), + errdetail("password type is not valid"))); } - /* Do frontend SCRAM auth first */ - authkind = doSCRAMAuth(frontend, 0, protoMajor); for (i=0;iprotoVersion, AUTH_REQ_PASSWORD, NULL, 0); + + /* Read password packet */ + read_password_packet(frontend, frontend->protoVersion, password, &size); + + /* save the password in frontend */ + frontend->auth_kind = AUTH_REQ_PASSWORD; + frontend->pwd_size = size; + memcpy(frontend->password, password, frontend->pwd_size); + frontend->password[size] = 0; /* Null terminate the password string */ + frontend->passwordType = PASSWORD_TYPE_PLAINTEXT; + + if (!frontend->passwordMapping) + { + /* + * if the password is not saved in pool_passwd + * just bail out from here + */ + return; + } + /* + * If we have md5 password stored in pool_passwd, convert the user + * supplied password to md5 for comparison + */ + if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_MD5) + { + pg_md5_encrypt(password, + frontend->username, + strlen(frontend->username), userPassword); + + pwd = userPassword; + } + else if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_PLAINTEXT) + { + pwd = password; + } + else + { + ereport(LOG, + (errmsg("password type stored in pool_passwd can't be used for clear text authentication"))); + return; + } + + if (memcmp(password, frontend->passwordMapping->pgpoolUser.password, size)) + { + /* Password does not match */ + ereport(ERROR, + (errmsg("clear text authentication failed"), + errdetail("password does not match"))); + } + ereport(LOG, + (errmsg("clear text authentication successfull with frontend"))); + + frontend->frontend_authenticated = true; +} + /* * perform clear text password authentication */ static int do_clear_text_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) { static int size; - static char password[MAX_PASSWORD_SIZE]; - char response; + char *pwd = NULL; int kind; - int len; - /* master? */ - if (IS_MASTER_NODE_ID(backend->db_node_id)) + if (reauth && frontend->frontend_authenticated) { - pool_write(frontend, "R", 1); /* authentication */ - if (protoMajor == PROTO_MAJOR_V3) - { - len = htonl(8); - pool_write(frontend, &len, sizeof(len)); - } - kind = htonl(3); /* clear text password authentication */ - pool_write_and_flush(frontend, &kind, sizeof(kind)); /* indicating clear text password authentication */ - - /* read password packet */ - if (protoMajor == PROTO_MAJOR_V2) - { - pool_read(frontend, &size, sizeof(size)); - } - else - { - char k; - - pool_read(frontend, &k, sizeof(k)); - if (k != 'p') - ereport(ERROR, - (errmsg("clear text password authentication failed"), - errdetail("invalid password packet. Packet does not starts with \"p\""))); - pool_read(frontend, &size, sizeof(size)); - } + /* frontend and backend are both authenticated already */ + return 0; + } - if ((ntohl(size) - 4) > sizeof(password)) - ereport(ERROR, - (errmsg("clear text password authentication failed"), - errdetail("password is too long. password size = %d", ntohl(size) - 4))); + /* get the password */ + if (frontend->pwd_size > 0) + { + pwd = frontend->password; + size = frontend->pwd_size; + } + else if (frontend->passwordMapping && frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_PLAINTEXT) + { + pwd = frontend->passwordMapping->pgpoolUser.password; + size = pwd? strlen(pwd): 0; + } - pool_read(frontend, password, ntohl(size) - 4); + if (pwd == NULL) + { + /* If we still do not have a password. we can't proceed */ + ereport(ERROR, + (errmsg("clear text password authentication failed"), + errdetail("unable to get the password"))); + return 0; } /* connection reusing? */ if (reauth) { - if ((ntohl(size) - 4) != backend->pwd_size) + if (size != backend->pwd_size) ereport(ERROR, - (errmsg("clear text password authentication failed"), + (errmsg("clear text password authentication failed"), errdetail("password size does not match"))); - if (memcmp(password, backend->password, backend->pwd_size) != 0) + if (memcmp(pwd, backend->password, backend->pwd_size) != 0) ereport(ERROR, - (errmsg("clear text password authentication failed"), + (errmsg("clear text password authentication failed"), errdetail("password does not match"))); return 0; } - /* send password packet to backend */ - if (protoMajor == PROTO_MAJOR_V3) - pool_write(backend, "p", 1); - pool_write(backend, &size, sizeof(size)); - pool_write_and_flush(backend, password, ntohl(size) -4); - - pool_read(backend, &response, sizeof(response)); - - if (response != 'R') - { - if(response == 'E') /* Backend has thrown an error instead */ - { - char* message = NULL; - if (pool_extract_error_message(false, backend, protoMajor, false, &message) == 1) - { - ereport(ERROR, - (errmsg("clear text password authentication failed"), - errdetail("%s",message?message:"backend throws authentication error"))); - } - if (message) - pfree(message); - } - ereport(ERROR, - (errmsg("clear text password authentication failed"), - errdetail("invalid packet from backend. backend does not return R while processing clear text password authentication"))); - } - if (protoMajor == PROTO_MAJOR_V3) - { - pool_read(backend, &len, sizeof(len)); - - if (ntohl(len) != 8) - ereport(ERROR, - (errmsg("clear text password authentication failed"), - errdetail("invalid packet from backend. incorrect authentication packet size (%d)", ntohl(len)))); - } - - /* expect to read "Authentication OK" response. kind should be 0... */ - pool_read(backend, &kind, sizeof(kind)); + kind = send_password_packet(backend, protoMajor, pwd); /* if authenticated, save info */ - if (!reauth && kind == 0) + if (!reauth && kind == AUTH_REQ_OK) { if (IS_MASTER_NODE_ID(backend->db_node_id)) { - int msglen; - - pool_write(frontend, "R", 1); - - if (protoMajor == PROTO_MAJOR_V3) - { - msglen = htonl(8); - pool_write(frontend, &msglen, sizeof(msglen)); - } - - msglen = htonl(0); - pool_write_and_flush(frontend, &msglen, sizeof(msglen)); + send_auth_ok(frontend, protoMajor); } - backend->auth_kind = 3; - backend->pwd_size = ntohl(size) - 4; - memcpy(backend->password, password, backend->pwd_size); + backend->auth_kind = AUTH_REQ_PASSWORD; + backend->pwd_size = size; + memcpy(backend->password, pwd, backend->pwd_size); + backend->passwordType = PASSWORD_TYPE_PLAINTEXT; } return kind; } @@ -867,9 +925,12 @@ static int do_crypt(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int rea return kind; } - -static int -doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) +/* + * Do the SCRAM authentication with the frontend using the stored + * password in the pool_passwd file. + */ +static void +authenticate_frontend_SCRAM(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth) { void *scram_opaq; char *output = NULL; @@ -880,14 +941,44 @@ doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) bool initial; char *logdetail = NULL; char *shadow_pass; - char *pool_passwd = pool_get_passwd(frontend->username); - if (!pool_passwd) + + PasswordType storedPasswordType = PASSWORD_TYPE_UNKNOWN; + char *storedPassword = NULL; + + if (!frontend->passwordMapping) + frontend->passwordMapping = pool_get_user_credentials(frontend->username); + + if (!frontend->passwordMapping) + { + /* see if we have a password stored in the backend for this */ + if (reauth && backend->pwd_size) + { + storedPasswordType = backend->passwordType; + storedPassword = backend->password; + } + else + { + ereport(FATAL, + (return_code(2), + errmsg("SCRAM authentication failed"), + errdetail("pool_passwd file does not contain an entry for \"%s\"",frontend->username))); + } + } + else + { + storedPasswordType = frontend->passwordMapping->pgpoolUser.passwordType; + storedPassword = frontend->passwordMapping->pgpoolUser.password; + } + + if (storedPasswordType != PASSWORD_TYPE_PLAINTEXT) + { ereport(ERROR, - (errmsg("authentication failed"), - errdetail("username \"%s\" does not exist in pool_passwd",frontend->username))); + (errmsg("SCRAM authentication failed"), + errdetail("username \"%s\" has invalid password type",frontend->username))); + } - shadow_pass = pg_be_scram_build_verifier(pool_passwd); + shadow_pass = pg_be_scram_build_verifier(storedPassword); if (!shadow_pass) ereport(ERROR, (errmsg("authentication failed"), @@ -902,7 +993,7 @@ doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) * without relying on the length word, but we hardly care about older * protocol version anymore.) */ - if (protoMajor < 3) + if (frontend->protoVersion < PROTO_MAJOR_V3) ereport(FATAL, (errmsg("SASL authentication is not supported in protocol version 2"))); @@ -913,7 +1004,7 @@ doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) * terminate the list. */ - sendAuthRequest(frontend, protoMajor, AUTH_REQ_SASL, SCRAM_SHA_256_NAME "\0", + sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_SASL, SCRAM_SHA_256_NAME "\0", strlen(SCRAM_SHA_256_NAME) + 2); /* @@ -942,7 +1033,7 @@ doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) static char data[MAX_PASSWORD_SIZE]; /* Read password packet */ - read_password_packet(frontend, protoMajor, data, &size); + read_password_packet(frontend, frontend->protoVersion, data, &size); ereport(DEBUG4, (errmsg("Processing received SASL response of length %d", size))); @@ -1005,9 +1096,9 @@ doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) (errmsg("sending SASL challenge of length %u", outputlen))); if (result == SASL_EXCHANGE_SUCCESS) - sendAuthRequest(frontend, protoMajor, AUTH_REQ_SASL_FIN, output, outputlen); + sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_SASL_FIN, output, outputlen); else - sendAuthRequest(frontend, protoMajor, AUTH_REQ_SASL_CONT, output, outputlen); + sendAuthRequest(frontend, frontend->protoVersion, AUTH_REQ_SASL_CONT, output, outputlen); pfree(output); } @@ -1020,154 +1111,347 @@ doSCRAMAuth(POOL_CONNECTION *frontend, int reauth, int protoMajor) (errmsg("authentication failed"), errdetail("username \"%s\" or password does not exist in backend",frontend->username))); } - - return 0; + frontend->frontend_authenticated = true; } +void authenticate_frontend(POOL_CONNECTION *frontend) +{ + switch (frontend->pool_hba->auth_method) + { + case uaMD5: + authenticate_frontend_md5(NULL,frontend,0,frontend->protoVersion); + break; + case uaCert: + authenticate_frontend_cert(frontend); + break; + case uaSCRAM: + authenticate_frontend_SCRAM(NULL, frontend, 0); + break; + case uaPassword: + authenticate_frontend_clear_text(frontend); + } +} -/* - * perform MD5 authentication - */ -static int do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) +#ifdef USE_SSL +static void authenticate_frontend_cert(POOL_CONNECTION *frontend) +{ + if (frontend->client_cert_loaded == true && frontend->cert_cn) + { + ereport(DEBUG1, + (errmsg("connecting user is \"%s\" and ssl certificate CN is \"%s\"",frontend->username,frontend->cert_cn))); + if (strcasecmp(frontend->username,frontend->cert_cn) == 0) + { + frontend->frontend_authenticated = true; + ereport(LOG, + (errmsg("SSL certificate authentication for user \"%s\" with Pgpool-II is successful",frontend->username))); + return POOL_CONTINUE; + } + else + { + frontend->frontend_authenticated = false; + ereport(LOG, + (errmsg("SSL certificate authentication for user \"%s\" failed",frontend->username))); + } + } + ereport(ERROR, + (errmsg("CERT authentication failed"), + errdetail("no valid certificate presented"))); + +} +#else +static void authenticate_frontend_cert(POOL_CONNECTION *frontend) +{ + ereport(ERROR, + (errmsg("CERT authentication failed"), + errdetail("CERT authentication is not supported without SSL"))); +} +#endif + +static void authenticate_frontend_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) { char salt[4]; static int size; - static char password[MAX_PASSWORD_SIZE]; - int kind; + char password[MAX_PASSWORD_SIZE]; + char userPassword[MAX_PASSWORD_SIZE]; char encbuf[POOL_PASSWD_LEN+1]; - char *pool_passwd = NULL; + char *md5; + + PasswordType storedPasswordType = PASSWORD_TYPE_UNKNOWN; + char *storedPassword = NULL; - if (!RAW_MODE && NUM_BACKENDS > 1) + if (RAW_MODE || NUM_BACKENDS == 1) { - /* Read password entry from pool_passwd */ - pool_passwd = pool_get_passwd(frontend->username); - if (!pool_passwd) - ereport(ERROR, - (errmsg("md5 authentication failed"), - errdetail("username \"%s\" does not exist in pool_passwd",frontend->username))); - + if (backend) + do_md5_single_backend(backend, frontend, reauth, protoMajor); + return; /* This will be handled later */ + } + if (!frontend->passwordMapping) + frontend->passwordMapping = pool_get_user_credentials(frontend->username); - /* master? */ - if (IS_MASTER_NODE_ID(backend->db_node_id)) + if (!frontend->passwordMapping) + { + /* see if we have a password stored in the backend for this */ + if (reauth && backend->pwd_size) { - /* Send md5 auth request to frontend with my own salt */ - pool_random_salt(salt); - send_md5auth_request(frontend, protoMajor, salt); - - /* Read password packet */ - read_password_packet(frontend, protoMajor, password, &size); - - /* Check the password using my salt + pool_passwd */ - pg_md5_encrypt(pool_passwd+strlen("md5"), salt, sizeof(salt), encbuf); - if (strcmp(password, encbuf)) - { - /* Password does not match */ - ereport(ERROR, - (errmsg("md5 authentication failed"), - errdetail("password does not match"))); - } + storedPasswordType = backend->passwordType; + storedPassword = backend->password; } - kind = 0; - - if (!reauth) + else { - /* - * If ok, authenticate against backends using pool_passwd - */ - /* Read salt */ - pool_read(backend, salt, sizeof(salt)); + ereport(FATAL, + (return_code(2), + errmsg("md5 authentication failed"), + errdetail("pool_passwd file does not contain an entry for \"%s\"",frontend->username))); + } + } + else + { + storedPasswordType = frontend->passwordMapping->pgpoolUser.passwordType; + storedPassword = frontend->passwordMapping->pgpoolUser.password; + } - ereport(DEBUG1, - (errmsg("performing md5 authentication"), - errdetail("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id, - salt[0], salt[1], salt[2], salt[3]))); + pool_random_salt(salt); + send_md5auth_request(frontend, frontend->protoVersion, salt); - /* Encrypt password in pool_passwd using the salt */ - pg_md5_encrypt(pool_passwd+strlen("md5"), salt, sizeof(salt), encbuf); + /* Read password packet */ + read_password_packet(frontend, frontend->protoVersion, password, &size); - /* Send password packet to backend and receive auth response */ - kind = send_password_packet(backend, protoMajor, encbuf); - if (kind < 0) - ereport(ERROR, - (errmsg("md5 authentication failed"), - errdetail("backend replied with invalid kind"))); - } + /*If we have clear text password stored in pool_passwd, convert it to md5 */ + if (storedPasswordType == PASSWORD_TYPE_PLAINTEXT) + { + pg_md5_encrypt(storedPassword, + frontend->username, + strlen(frontend->username), userPassword); - if (!reauth && kind == 0) - { - if (IS_MASTER_NODE_ID(backend->db_node_id)) - { - /* Send auth ok to frontend */ - send_auth_ok(frontend, protoMajor); - } + md5 = userPassword; + } + else if (storedPasswordType == PASSWORD_TYPE_MD5) + { + md5 = storedPassword; + } + else + { + ereport(FATAL, + (return_code(2), + errmsg("md5 authentication failed"), + errdetail("unable to get the password for \"%s\"",frontend->username))); - /* Save the auth info */ - backend->auth_kind = AUTH_REQ_MD5; - } - return kind; } + /* Check the password using my salt + pool_passwd */ + pg_md5_encrypt(md5+strlen("md5"), salt, sizeof(salt), encbuf); + if (strcmp(password, encbuf)) + { + /* Password does not match */ + ereport(ERROR, + (errmsg("md5 authentication failed"), + errdetail("password does not match"))); + } + ereport(LOG, + (errmsg("md5 authentication successfull with frontend"))); - /* - * Followings are NUM_BACKEND == 1 case. - */ + frontend->frontend_authenticated = true; +} + +/* + * perform MD5 authentication + */ +static int do_md5_single_backend(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor) +{ + char salt[4]; + static int size; + static char password[MAX_PASSWORD_SIZE]; + int kind; + if (!reauth) { - /* read salt */ + /* read salt from backend */ pool_read(backend, salt, sizeof(salt)); ereport(DEBUG1, - (errmsg("performing md5 authentication"), - errdetail("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id, - salt[0], salt[1], salt[2], salt[3]))); + (errmsg("performing md5 authentication"), + errdetail("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id, + salt[0], salt[1], salt[2], salt[3]))); } else { + /* Use the saved salt */ memcpy(salt, backend->salt, sizeof(salt)); } + + /* Send md5 auth request to frontend */ + send_md5auth_request(frontend, protoMajor, salt); - /* master? */ - if (IS_MASTER_NODE_ID(backend->db_node_id)) - { - /* Send md5 auth request to frontend */ - send_md5auth_request(frontend, protoMajor, salt); - - /* Read password packet */ - read_password_packet(frontend, protoMajor, password, &size); - } + /* Read password packet */ + read_password_packet(frontend, protoMajor, password, &size); - /* connection reusing? */ + /* connection reusing? compare it with saved password */ if (reauth) { + if (backend->passwordType != PASSWORD_TYPE_MD5) + ereport(ERROR, + (errmsg("md5 authentication failed"), + errdetail("invalid password type"))); + if (size != backend->pwd_size) - ereport(ERROR, - (errmsg("md5 authentication failed"), - errdetail("password does not match"))); + ereport(ERROR, + (errmsg("md5 authentication failed"), + errdetail("password does not match"))); if (memcmp(password, backend->password, backend->pwd_size) != 0) - ereport(ERROR, - (errmsg("md5 authentication failed"), - errdetail("password does not match"))); - + ereport(ERROR, + (errmsg("md5 authentication failed"), + errdetail("password does not match"))); return 0; } + else + { + /* Send password packet to backend and receive auth response */ + kind = send_password_packet(backend, protoMajor, password); + if (kind < 0) + ereport(ERROR, + (errmsg("md5 authentication failed"), + errdetail("backend replied with invalid kind"))); - /* Send password packet to backend and receive auth response */ - kind = send_password_packet(backend, protoMajor, password); - if (kind < 0) - ereport(ERROR, + /* If authenticated, reply back to frontend and save info */ + if (kind == 0) + { + send_auth_ok(frontend, protoMajor); + backend->passwordType = PASSWORD_TYPE_MD5; + backend->auth_kind = AUTH_REQ_MD5; + backend->pwd_size = size; + memcpy(backend->password, password, backend->pwd_size); + memcpy(backend->salt, salt, sizeof(salt)); + } + } + return kind; +} + +static bool get_auth_password(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, + char** password, PasswordType *passwordType) +{ + /* First preference is to use the pool_passwd file */ + if (frontend->passwordMapping == NULL) + frontend->passwordMapping = pool_get_user_credentials(frontend->username); + + if (frontend->passwordMapping == NULL) + { + /* + * check if we have password stored in the + * frontend connection. + * that could come by using the clear text auth + */ + if (frontend->pwd_size > 0 && frontend->passwordType == PASSWORD_TYPE_PLAINTEXT) + { + *password = frontend->password; + *passwordType = frontend->passwordType; + return true; + } + else if (reauth && backend && backend->pwd_size > 0) + { + *password = backend->password; + *passwordType = backend->passwordType; + return true; + } + } + else + { + *password = frontend->passwordMapping->pgpoolUser.password; + *passwordType = frontend->passwordMapping->pgpoolUser.passwordType; + return true; + } + return false; +} + +/* + * perform MD5 authentication + */ +static int do_md5(POOL_CONNECTION *backend, POOL_CONNECTION *frontend, int reauth, int protoMajor, + char *storedPassword, PasswordType passwordType) +{ + char salt[4]; + static char userPassword[MAX_PASSWORD_SIZE]; + int kind; + char encbuf[POOL_PASSWD_LEN+1]; + char *pool_passwd = NULL; + + if (NUM_BACKENDS == 1) + return do_md5_single_backend(backend, frontend, reauth, protoMajor); + + if (passwordType == PASSWORD_TYPE_PLAINTEXT) + { + pg_md5_encrypt(storedPassword, + frontend->username, + strlen(frontend->username), userPassword); + pool_passwd = userPassword; + } + else if (frontend->passwordMapping->pgpoolUser.passwordType == PASSWORD_TYPE_MD5) + { + pool_passwd = storedPassword; + } + else + { + ereport(ERROR, (errmsg("md5 authentication failed"), - errdetail("backend replied with invalid kind"))); + errdetail("unable to get the password"))); + } + + /* master? */ + if (IS_MASTER_NODE_ID(backend->db_node_id) && frontend->frontend_authenticated == false ) + { + /* frontend is not yet authenticated, Do it now */ + if (frontend->passwordType != PASSWORD_TYPE_UNKNOWN && + frontend->auth_kind != AUTH_REQ_PASSWORD) + { + authenticate_frontend_md5(backend, frontend, reauth, protoMajor); + } + else + { + ereport(DEBUG2, + (errmsg("md5 authentication using the password from frontend"))); + /* save this password in backend for the re-auth */ + backend->pwd_size = frontend->pwd_size; + memcpy(backend->password, frontend->password, frontend->pwd_size); + backend->password[backend->pwd_size] = 0; /* null terminate */ + backend->passwordType = frontend->passwordType; + } + } + + if (!reauth) + { + /* + * now authenticate the backend + */ + + /* Read salt */ + pool_read(backend, salt, sizeof(salt)); + + ereport(DEBUG2, + (errmsg("performing md5 authentication"), + errdetail("DB node id: %d salt: %hhx%hhx%hhx%hhx", backend->db_node_id, + salt[0], salt[1], salt[2], salt[3]))); + + /* Encrypt password in pool_passwd using the salt */ + pg_md5_encrypt(pool_passwd+strlen("md5"), salt, sizeof(salt), encbuf); + + /* Send password packet to backend and receive auth response */ + kind = send_password_packet(backend, protoMajor, encbuf); + if (kind < 0) + ereport(ERROR, + (errmsg("md5 authentication failed"), + errdetail("backend replied with invalid kind"))); + } - /* If authenticated, reply back to frontend and save info */ if (!reauth && kind == 0) { - send_auth_ok(frontend, protoMajor); + if (IS_MASTER_NODE_ID(backend->db_node_id)) + { + /* Send auth ok to frontend */ + send_auth_ok(frontend, protoMajor); + } - backend->auth_kind = 5; - backend->pwd_size = size; - memcpy(backend->password, password, backend->pwd_size); - memcpy(backend->salt, salt, sizeof(salt)); + /* Save the auth info */ + backend->auth_kind = AUTH_REQ_MD5; } - return kind; + return 0; } /* @@ -1188,6 +1472,8 @@ sendAuthRequest(POOL_CONNECTION *frontend, int protoMajor, int32 auth_req_type, pool_write(frontend, &kind, sizeof(kind)); if (extralen > 0) pool_write_and_flush(frontend, extradata, extralen); + else + pool_flush(frontend); } /* @@ -1577,17 +1863,49 @@ PostmasterRandom(void) } -/* SCRAM CLIENT */ -/* - * Initialize SASL authentication exchange. - */ -static bool doSCRAMBackendAuth(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, int protoMajor, int message_length) +static bool do_SCRAM(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, int protoMajor, int message_length, + char *storedPassword, PasswordType passwordType) { /* read the packet first */ void *sasl_state = NULL; int payload_len = message_length - 4 - 4; int auth_kind = AUTH_REQ_SASL; char payload[1024]; + + if (passwordType != PASSWORD_TYPE_PLAINTEXT) + { + ereport(ERROR, + (errmsg("SCRAM authentication failed"), + errdetail("invalid password type"))); + } + if (storedPassword == NULL) + { + ereport(ERROR, + (errmsg("SCRAM authentication failed"), + errdetail("password not found"))); + } + + /* master? */ + if (IS_MASTER_NODE_ID(backend->db_node_id) && frontend->frontend_authenticated == false ) + { + /* frontend is not yet authenticated, Do it now */ + if (frontend->passwordType != PASSWORD_TYPE_UNKNOWN && + frontend->auth_kind != AUTH_REQ_PASSWORD) + { + authenticate_frontend_SCRAM(backend, frontend, 0); + } + else + { + ereport(DEBUG2, + (errmsg("SCRAM authentication using the password from frontend"))); + /* save this password in backend for the re-auth */ + backend->pwd_size = frontend->pwd_size; + memcpy(backend->password, frontend->password, frontend->pwd_size); + backend->password[backend->pwd_size] = 0; /* null terminate */ + backend->passwordType = frontend->passwordType; + } + } + for(;;) { /* at this point we have already read kind, message length and authkind */ @@ -1597,10 +1915,10 @@ static bool doSCRAMBackendAuth(POOL_CONNECTION *frontend, POOL_CONNECTION *backe pool_read(backend, payload, payload_len); - //char kind = read_packet_from_connection(backend, protoMajor, data, &len); - switch (auth_kind) { case AUTH_REQ_OK: + /* Save the auth info in backend */ + backend->auth_kind = AUTH_REQ_SASL; return true; break; case AUTH_REQ_SASL: @@ -1609,7 +1927,7 @@ static bool doSCRAMBackendAuth(POOL_CONNECTION *frontend, POOL_CONNECTION *backe * The request contains the name (as assigned by IANA) of the * authentication mechanism. */ - sasl_state = pg_SASL_init(frontend, backend, payload, payload_len); + sasl_state = pg_SASL_init(frontend, backend, payload, payload_len, storedPassword); if (!sasl_state) { ereport(ERROR, @@ -1661,7 +1979,7 @@ static bool doSCRAMBackendAuth(POOL_CONNECTION *frontend, POOL_CONNECTION *backe } static void* -pg_SASL_init(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, char *payload, int payloadlen) +pg_SASL_init(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, char *payload, int payloadlen, char* storedPassword) { char *initialresponse = NULL; int initialresponselen; @@ -1701,16 +2019,13 @@ pg_SASL_init(POOL_CONNECTION *frontend, POOL_CONNECTION *backend, char *payload, * for authentication. * It is stored in the file */ - /* Read the password from the pool_passwd file */ - char *password = pool_get_passwd(frontend->username); - - if (password == NULL || password[0] == '\0') + if (storedPassword == NULL || storedPassword[0] == '\0') { ereport(ERROR, (errmsg("password not found"))); } - sasl_state = pg_fe_scram_init(frontend->username, password); + sasl_state = pg_fe_scram_init(frontend->username, storedPassword); if (!sasl_state) ereport(ERROR, (errmsg("SASL authentication error\n"))); diff --git a/src/auth/pool_hba.c b/src/auth/pool_hba.c index 6d5f082b6..f5728fc03 100644 --- a/src/auth/pool_hba.c +++ b/src/auth/pool_hba.c @@ -615,6 +615,10 @@ parse_hba_line(TokenizedLine *tok_line, int elevel) parsedline->auth_method = uaTrust; else if (strcmp(token->string, "reject") == 0) parsedline->auth_method = uaReject; + else if (strcmp(token->string, "cert") == 0) + parsedline->auth_method = uaCert; + else if (strcmp(token->string, "password") == 0) + parsedline->auth_method = uaPassword; else if (strcmp(token->string, "md5") == 0) parsedline->auth_method = uaMD5; else if (strcmp(token->string, "scram-sha-256") == 0) @@ -769,7 +773,11 @@ void ClientAuthentication(POOL_CONNECTION *frontend) errdetail("missing or erroneous pool_hba.conf file"), errhint("see pgpool log for details"))); - + /* + * Get the password for the user if it is stored + * in the pool_password file + */ + frontend->passwordMapping = pool_get_user_credentials(frontend->username); switch (frontend->pool_hba->auth_method) { case uaImplicitReject: @@ -818,27 +826,51 @@ void ClientAuthentication(POOL_CONNECTION *frontend) case uaKrb5: case uaIdent: case uaCrypt: + */ case uaPassword: - break; - */ + ereport(DEBUG1, + (errmsg("password authentication required"))); + status = POOL_CONTINUE; + break; + case uaCert: + ereport(DEBUG1, + (errmsg("SSL certificate authentication required"))); + status = POOL_CONTINUE; + break; case uaMD5: - status = CheckUserExist(frontend->username); - if (status != POOL_CONTINUE) + status = POOL_CONTINUE; + + if (NUM_BACKENDS <= 1) + break; + + if (!frontend->passwordMapping) ereport(FATAL, (return_code(2), - errmsg("md5 authentication failed"), + errmsg("md5 authentication failed"), errdetail("pool_passwd file does not contain an entry for \"%s\"",frontend->username))); + if (frontend->passwordMapping->pgpoolUser.passwordType != PASSWORD_TYPE_PLAINTEXT && + frontend->passwordMapping->pgpoolUser.passwordType != PASSWORD_TYPE_MD5) + ereport(FATAL, + (return_code(2), + errmsg("md5 authentication failed"), + errdetail("pool_passwd file does not contain valid md5 entry for \"%s\"",frontend->username))); break; case uaSCRAM: - status = CheckUserExist(frontend->username); - if (status != POOL_CONTINUE) + if (!frontend->passwordMapping) ereport(FATAL, (return_code(2), errmsg("SCRAM authentication failed"), errdetail("pool_passwd file does not contain an entry for \"%s\"",frontend->username))); + if (frontend->passwordMapping->pgpoolUser.passwordType != PASSWORD_TYPE_PLAINTEXT && + frontend->passwordMapping->pgpoolUser.passwordType != PASSWORD_TYPE_SCRAM_SHA_256) + ereport(FATAL, + (return_code(2), + errmsg("SCRAM authentication failed"), + errdetail("pool_passwd file does not contain valid SCRAM entry for \"%s\"",frontend->username))); + status = POOL_CONTINUE; break; @@ -853,6 +885,7 @@ void ClientAuthentication(POOL_CONNECTION *frontend) status = POOL_CONTINUE; break; } + authenticate_frontend(frontend); } PG_CATCH(); { @@ -1012,6 +1045,12 @@ static void auth_failed(POOL_CONNECTION *frontend) frontend->username); break; + case uaCert: + snprintf(errmessage, messagelen, + "\"CERT\" authentication with pgpool failed for user \"%s\"", + frontend->username); + break; + /* case uaCrypt: */ /* case uaPassword: */ /* snprintf(errmessage, messagelen, */ @@ -2006,6 +2045,8 @@ static POOL_STATUS CheckPAMAuth(POOL_CONNECTION *frontend, char *user, char *pas #endif /* USE_PAM */ + + static POOL_STATUS CheckUserExist(char *username) { char *passwd; diff --git a/src/auth/pool_passwd.c b/src/auth/pool_passwd.c index 4dd30d160..dab815d1c 100644 --- a/src/auth/pool_passwd.c +++ b/src/auth/pool_passwd.c @@ -233,6 +233,158 @@ char *pool_get_passwd(char *username) return NULL; } +/* + * return the next token if the current token matches the + * user + */ +static char * +getNextToken(char *buf, char **token) +{ +#define MAX_TOKEN_LEN 128 + char *tbuf,*p; + bool bslash = false; + char tok[MAX_TOKEN_LEN+1]; + int readlen = 0; + + *token = NULL; + if (buf == NULL) + return NULL; + + tbuf = buf; + p = tok; + while (*tbuf != 0 && readlen < MAX_TOKEN_LEN) + { + if (*tbuf == '\\' && !bslash) + { + tbuf++; + bslash = true; + } + + if (*tbuf == ':' && !bslash) + { + *p = '\0'; + if(readlen) + *token = pstrdup(tok); + return tbuf + 1; + } + /* just copy to the tok */ + bslash = false; + *p++ = *tbuf++; + readlen++; + } + *p = '\0'; + if(readlen) + *token = pstrdup(tok); + return NULL; +} +/* + * return the next token if the current token matches the + * user + */ +static char * +userMatchesString(char *buf, char *user) +{ + char *tbuf, + *ttok; + bool bslash = false; + + if (buf == NULL || user == NULL) + return NULL; + tbuf = buf; + ttok = user; + while (*tbuf != 0) + { + if (*tbuf == '\\' && !bslash) + { + tbuf++; + bslash = true; + } + if (*tbuf == ':' && *ttok == 0 && !bslash) + return tbuf + 1; + bslash = false; + if (*ttok == 0) + return NULL; + if (*tbuf == *ttok) + { + tbuf++; + ttok++; + } + else + return NULL; + } + return NULL; +} + +/* + * user:passwod[:user:password] + */ +PasswordMapping *pool_get_user_credentials(char *username) +{ + PasswordMapping *pwdMapping = NULL; + char buf[1024]; + + + if (!username) + ereport(ERROR, + (errmsg("unable to get password, username is NULL"))); + + if (!passwd_fd) + ereport(ERROR, + (errmsg("unable to get password, password file descriptor is NULL"))); + + rewind(passwd_fd); + + while (!feof(passwd_fd) && !ferror(passwd_fd)) + { + char *t = buf; + char *tok; + int len; + + if (fgets(buf, sizeof(buf), passwd_fd) == NULL) + break; + + len = strlen(buf); + if (len == 0) + continue; + + /* Remove trailing newline */ + if (buf[len - 1] == '\n') + buf[len - 1] = 0; + + if ((t = userMatchesString(t, username)) == NULL) + continue; + /* Get the password */ + t = getNextToken(t, &tok); + if (tok) + { + pwdMapping = palloc(sizeof(PasswordMapping)); + pwdMapping->pgpoolUser.password = tok; + pwdMapping->pgpoolUser.passwordType = get_password_type(pwdMapping->pgpoolUser.password); + pwdMapping->pgpoolUser.userName = pstrdup(username); + pwdMapping->mappedUser = false; + } + else + continue; + /* Get backend user*/ + t = getNextToken(t, &tok); + if (tok) + { + /* check if we also have the password */ + char *pwd; + t = getNextToken(t, &pwd); + if (tok) + { + pwdMapping->backendUser.password = pwd; + pwdMapping->backendUser.userName = tok; + pwdMapping->backendUser.passwordType = get_password_type(pwdMapping->backendUser.password); + pwdMapping->mappedUser = true; + } + } + break; + } + return pwdMapping; +} + /* * Delete the entry by username. If specified entry does not exist, * does nothing. diff --git a/src/include/auth/pool_hba.h b/src/include/auth/pool_hba.h index ba5cf2de3..a4588a8a7 100644 --- a/src/include/auth/pool_hba.h +++ b/src/include/auth/pool_hba.h @@ -38,8 +38,9 @@ typedef enum UserAuth /* uaKrb5, */ uaTrust, /* uaIdent, */ - /* uaPassword, */ + uaPassword, /* uaCrypt, */ + uaCert, uaMD5, uaSCRAM #ifdef USE_PAM diff --git a/src/include/auth/pool_passwd.h b/src/include/auth/pool_passwd.h index 2368364b4..45f82dcac 100644 --- a/src/include/auth/pool_passwd.h +++ b/src/include/auth/pool_passwd.h @@ -6,7 +6,7 @@ * pgpool: a language independent connection pool server for PostgreSQL * written by Tatsuo Ishii * - * Copyright (c) 2003-2015 PgPool Global Development Group + * Copyright (c) 2003-2018 PgPool Global Development Group * * Permission to use, copy, modify, and distribute this software and * its documentation for any purpose and without fee is hereby @@ -26,7 +26,6 @@ #ifndef POOL_PASSWD_H #define POOL_PASSWD_H -#include "pool.h" #define POOL_PASSWD_FILENAME "pool_passwd" #define POOL_PASSWD_LEN 35 @@ -38,11 +37,27 @@ typedef enum { typedef enum PasswordType { - PASSWORD_TYPE_PLAINTEXT = 0, + PASSWORD_TYPE_UNKNOWN = 0, + PASSWORD_TYPE_PLAINTEXT, PASSWORD_TYPE_MD5, PASSWORD_TYPE_SCRAM_SHA_256 } PasswordType; +typedef struct UserPassword +{ + char *userName; + char *password; + PasswordType passwordType; +}UserPassword; + +typedef struct PasswordMapping +{ + UserPassword pgpoolUser; + UserPassword backendUser; + bool mappedUser; +}PasswordMapping; + +extern PasswordMapping *pool_get_user_credentials(char *username); extern PasswordType get_password_type(const char *shadow_pass); extern void pool_init_pool_passwd(char *pool_passwd_filename, POOL_PASSWD_MODE mode); extern int pool_create_passwdent(char *username, char *passwd); diff --git a/src/include/pool.h b/src/include/pool.h index d9c6ff2c3..7c8671228 100644 --- a/src/include/pool.h +++ b/src/include/pool.h @@ -28,6 +28,7 @@ #include "pool_type.h" #include "pcp/libpcp_ext.h" #include "utils/pool_signal.h" +#include "auth/pool_passwd.h" #include "parser/nodes.h" #include #include @@ -181,6 +182,13 @@ typedef struct { #ifdef USE_SSL SSL_CTX *ssl_ctx; /* SSL connection context */ SSL *ssl; /* SSL connection */ + X509 *peer; + char *cert_cn; /* common in the ssl certificate + * presented by frontend connection + * Used for cert authentication + */ + bool client_cert_loaded; + #endif int ssl_active; /* SSL is failed if < 0, off if 0, on if > 0 */ @@ -215,8 +223,9 @@ typedef struct { */ int auth_kind; /* 3: clear text password, 4: crypt password, 5: md5 password */ int pwd_size; /* password (sent back from frontend) size in host order */ - char password[MAX_PASSWORD_SIZE]; /* password (sent back from frontend) */ + char password[MAX_PASSWORD_SIZE +1 ]; /* password (sent back from frontend) */ char salt[4]; /* password salt */ + PasswordType passwordType; /* * following are used to remember current session parameter status. @@ -240,6 +249,8 @@ typedef struct { char *username; char *remote_hostname; int remote_hostname_resolv; + bool frontend_authenticated; + PasswordMapping *passwordMapping; ConnectionInfo *con_info; /* shared memory coninfo used * for handling the query containing * pg_terminate_backend*/ @@ -555,6 +566,7 @@ extern POOL_STATUS pool_process_query(POOL_CONNECTION *frontend, extern int pool_do_auth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *backend); extern int pool_do_reauth(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *cp); +extern void authenticate_frontend(POOL_CONNECTION *frontend); extern bool is_backend_cache_empty(POOL_CONNECTION_POOL *backend); @@ -565,6 +577,7 @@ extern void pool_ssl_close(POOL_CONNECTION *cp); extern int pool_ssl_read(POOL_CONNECTION *cp, void *buf, int size); extern int pool_ssl_write(POOL_CONNECTION *cp, const void *buf, int size); extern bool pool_ssl_pending(POOL_CONNECTION *cp); +extern int SSL_ServerSide_init(void); extern POOL_STATUS ErrorResponse(POOL_CONNECTION *frontend, POOL_CONNECTION_POOL *backend); diff --git a/src/main/main.c b/src/main/main.c index a87fa2965..21123cff8 100644 --- a/src/main/main.c +++ b/src/main/main.c @@ -184,15 +184,15 @@ int main(int argc, char **argv) exit(1); } } -#ifdef USE_SSL - /* global ssl init */ -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined (LIBRESSL_VERSION_NUMBER)) - OPENSSL_init_ssl(0, NULL); -#else - SSL_library_init(); -#endif - SSL_load_error_strings(); -#endif /* USE_SSL */ +//#ifdef USE_SSL +// /* global ssl init */ +//#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined (LIBRESSL_VERSION_NUMBER)) +// OPENSSL_init_ssl(0, NULL); +//#else +// SSL_library_init(); +//#endif +// SSL_load_error_strings(); +//#endif /* USE_SSL */ myargv = save_ps_display_args(myargc, myargv); /* create MemoryContexts */ @@ -284,6 +284,10 @@ int main(int argc, char **argv) if (pool_config->enable_pool_hba) load_hba(hba_file); +#ifdef USE_SSL + SSL_ServerSide_init(); +#endif /* USE_SSL */ + /* check effective user id for watchdog */ /* watchdog must be started under the privileged user */ wd_check_network_command_configurations(); diff --git a/src/main/pgpool_main.c b/src/main/pgpool_main.c index 5b73dbf8d..981492461 100644 --- a/src/main/pgpool_main.c +++ b/src/main/pgpool_main.c @@ -46,7 +46,6 @@ #include #include "utils/elog.h" -#include "utils/palloc.h" #include "pool.h" #include "utils/palloc.h" diff --git a/src/protocol/child.c b/src/protocol/child.c index 4a970c525..2f18a7eaa 100644 --- a/src/protocol/child.c +++ b/src/protocol/child.c @@ -2317,7 +2317,7 @@ retry_startup: found = 0; backend = pool_get_cp(sp->user, sp->database, sp->major, 1); - + if (backend != NULL) { found = 1; diff --git a/src/utils/pool_ssl.c b/src/utils/pool_ssl.c index 1b2de402e..ae01da606 100644 --- a/src/utils/pool_ssl.c +++ b/src/utils/pool_ssl.c @@ -27,11 +27,22 @@ #include "config.h" #include "pool.h" #include "utils/elog.h" +#include "utils/palloc.h" +#include "utils/memutils.h" #include "utils/pool_stream.h" #include "pool_config.h" +#include #ifdef USE_SSL +static SSL_CTX *SSL_frontend_context = NULL; +static bool SSL_initialized = false; +static bool ssl_passwd_cb_called = false; +static int ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata); +static int verify_cb(int ok, X509_STORE_CTX *ctx); +static const char * SSLerrmessage(unsigned long ecode); +static void fetch_pool_ssl_cert(POOL_CONNECTION *cp); + #define SSL_RETURN_VOID_IF(cond, msg) \ do { \ if ( (cond) ) { \ @@ -62,7 +73,9 @@ static int init_ssl_ctx(POOL_CONNECTION *cp, enum ssl_conn_type conntype); /* OpenSSL error message */ static void perror_ssl(const char *context); -/* attempt to negotiate a secure connection */ +/* Attempt to negotiate a secure connection + * between pgpool-II and PostgreSQL backends + */ void pool_ssl_negotiate_clientserver(POOL_CONNECTION *cp) { int ssl_packet[2] = { htonl(sizeof(int)*2), htonl(NEGOTIATE_SSL_CODE) }; char server_response; @@ -112,21 +125,27 @@ void pool_ssl_negotiate_clientserver(POOL_CONNECTION *cp) { } -/* attempt to negotiate a secure connection */ +/* attempt to negotiate a secure connection + * between frontend and Pgpool-II + */ void pool_ssl_negotiate_serverclient(POOL_CONNECTION *cp) { cp->ssl_active = -1; + if ( (!pool_config->ssl) || !SSL_frontend_context) { - if ( (!pool_config->ssl) || init_ssl_ctx(cp, ssl_conn_serverclient)) { +// if ( (!pool_config->ssl) || init_ssl_ctx(cp, ssl_conn_serverclient)) { /* write back an "SSL reject" response before returning */ pool_write_and_flush(cp, "N", 1); } else { + cp->ssl = SSL_new(SSL_frontend_context); + /* write back an "SSL accept" response */ pool_write_and_flush(cp, "S", 1); SSL_set_fd(cp->ssl, cp->fd); SSL_RETURN_VOID_IF( (SSL_accept(cp->ssl) < 0), "SSL_accept"); cp->ssl_active = 1; + fetch_pool_ssl_cert(cp); } } @@ -318,6 +337,31 @@ static void perror_ssl(const char *context) { } } +/* + * Obtain reason string for passed SSL errcode + * + * ERR_get_error() is used by caller to get errcode to pass here. + * + * Some caution is needed here since ERR_reason_error_string will + * return NULL if it doesn't recognize the error code. We don't + * want to return NULL ever. + */ +static const char * +SSLerrmessage(unsigned long ecode) +{ + const char *errreason; + static char errbuf[32]; + + if (ecode == 0) + return _("no SSL error reported"); + errreason = ERR_reason_error_string(ecode); + if (errreason != NULL) + return errreason; + snprintf(errbuf, sizeof(errbuf), _("SSL error code %lu"), ecode); + return errbuf; +} + + /* * Return true if SSL layer has any pending data in buffer */ @@ -328,6 +372,315 @@ bool pool_ssl_pending(POOL_CONNECTION *cp) return false; } +static void fetch_pool_ssl_cert(POOL_CONNECTION *cp) +{ + int len; + X509 *peer = SSL_get_peer_certificate(cp->ssl); + cp->peer = peer; + if (peer) + { + ereport(DEBUG1, + (errmsg("got the SSL certificate"))); + len = X509_NAME_get_text_by_NID(X509_get_subject_name(peer),NID_commonName, NULL, 0); + if (len != -1) + { + char *peer_cn; + peer_cn = palloc(len + 1); + int r = X509_NAME_get_text_by_NID(X509_get_subject_name(peer), NID_commonName, peer_cn, len + 1); + peer_cn[len] = '\0'; + if (r != len) + { + /* shouldn't happen */ + pfree(peer_cn); + return; + } + cp->client_cert_loaded = true; + cp->cert_cn = MemoryContextStrdup(TopMemoryContext,peer_cn); + pfree(peer_cn); + } + else + { + cp->client_cert_loaded = false; + } + } + else + { + cp->client_cert_loaded = false; + } +} + +/* + * Passphrase collection callback + * + * If OpenSSL is told to use a passphrase-protected server key, by default + * it will issue a prompt on /dev/tty and try to read a key from there. + * That's no good during a postmaster SIGHUP cycle, not to mention SSL context + * reload in an EXEC_BACKEND postmaster child. So override it with this dummy + * function that just returns an empty passphrase, guaranteeing failure. + */ +static int +ssl_passwd_cb(char *buf, int size, int rwflag, void *userdata) +{ + /* Set flag to change the error message we'll report */ + ssl_passwd_cb_called = true; + /* And return empty string */ + Assert(size > 0); + buf[0] = '\0'; + return 0; +} +/* + * Certificate verification callback + * + * This callback allows us to log intermediate problems during + * verification, but for now we'll see if the final error message + * contains enough information. + * + * This callback also allows us to override the default acceptance + * criteria (e.g., accepting self-signed or expired certs), but + * for now we accept the default checks. + */ +static int +verify_cb(int ok, X509_STORE_CTX *ctx) +{ + return ok; +} + +/* + * Initialize global SSL context. + * + * If isServerStart is true, report any errors as FATAL (so we don't return). + * Otherwise, log errors at LOG level and return -1 to indicate trouble, + * preserving the old SSL state if any. Returns 0 if OK. + */ +int +SSL_ServerSide_init(void) +{ + STACK_OF(X509_NAME) *root_cert_list = NULL; + SSL_CTX *context; + struct stat buf; + + /* This stuff need be done only once. */ + if (!SSL_initialized) + { +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined (LIBRESSL_VERSION_NUMBER)) + OPENSSL_init_ssl(0, NULL); +#else + SSL_library_init(); +#endif + SSL_load_error_strings(); + + SSL_initialized = true; + } + + /* + * We use SSLv23_method() because it can negotiate use of the highest + * mutually supported protocol version, while alternatives like + * TLSv1_2_method() permit only one specific version. Note that we don't + * actually allow SSL v2 or v3, only TLS protocols (see below). + */ +#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined (LIBRESSL_VERSION_NUMBER)) + context = SSL_CTX_new(TLS_method()); +#else + context = SSL_CTX_new(SSLv23_method()); +#endif + + if (!context) + { + ereport(FATAL, + (errmsg("could not create SSL context: %s", + SSLerrmessage(ERR_get_error())))); + goto error; + } + + /* + * Disable OpenSSL's moving-write-buffer sanity check, because it causes + * unnecessary failures in nonblocking send cases. + */ + SSL_CTX_set_mode(context, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + + /* + * prompt for password for passphrase-protected files + */ + SSL_CTX_set_default_passwd_cb(context, ssl_passwd_cb); + + /* + * Load and verify server's certificate and private key + */ + if (SSL_CTX_use_certificate_chain_file(context, pool_config->ssl_cert) != 1) + { + ereport(FATAL, + (errmsg("could not load server certificate file \"%s\": %s", + pool_config->ssl_cert, SSLerrmessage(ERR_get_error())))); + goto error; + } + + if (stat(pool_config->ssl_key, &buf) != 0) + { + ereport(FATAL, + (errmsg("could not access private key file \"%s\": %m", + pool_config->ssl_key))); + goto error; + } + + if (!S_ISREG(buf.st_mode)) + { + ereport(FATAL, + (errmsg("private key file \"%s\" is not a regular file", + pool_config->ssl_key))); + goto error; + } + + /* + * Refuse to load key files owned by users other than us or root. + * + * XXX surely we can check this on Windows somehow, too. + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + if (buf.st_uid != geteuid() && buf.st_uid != 0) + { + ereport(FATAL, + (errmsg("private key file \"%s\" must be owned by the database user or root", + pool_config->ssl_key))); + goto error; + } +#endif + + /* + * Require no public access to key file. If the file is owned by us, + * require mode 0600 or less. If owned by root, require 0640 or less to + * allow read access through our gid, or a supplementary gid that allows + * to read system-wide certificates. + * + * XXX temporarily suppress check when on Windows, because there may not + * be proper support for Unix-y file permissions. Need to think of a + * reasonable check to apply on Windows. (See also the data directory + * permission check in postmaster.c) + */ +#if !defined(WIN32) && !defined(__CYGWIN__) + if ((buf.st_uid == geteuid() && buf.st_mode & (S_IRWXG | S_IRWXO)) || + (buf.st_uid == 0 && buf.st_mode & (S_IWGRP | S_IXGRP | S_IRWXO))) + { + ereport(FATAL, + (errmsg("private key file \"%s\" has group or world access", + pool_config->ssl_key), + errdetail("File must have permissions u=rw (0600) or less if owned by the database user, or permissions u=rw,g=r (0640) or less if owned by root."))); + goto error; + } +#endif + + /* + * OK, try to load the private key file. + */ + ssl_passwd_cb_called = false; + + if (SSL_CTX_use_PrivateKey_file(context, + pool_config->ssl_key, + SSL_FILETYPE_PEM) != 1) + { + if (ssl_passwd_cb_called) + ereport(FATAL, + (errmsg("private key file \"%s\" cannot be reloaded because it requires a passphrase", + pool_config->ssl_key))); + else + ereport(FATAL, + (errmsg("could not load private key file \"%s\": %s", + pool_config->ssl_key, SSLerrmessage(ERR_get_error())))); + goto error; + } + + if (SSL_CTX_check_private_key(context) != 1) + { + ereport(FATAL, + (errmsg("check of private key failed: %s", + SSLerrmessage(ERR_get_error())))); + goto error; + } + + /* disallow SSL v2/v3 */ + SSL_CTX_set_options(context, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); + + /* disallow SSL session tickets */ +#ifdef SSL_OP_NO_TICKET /* added in openssl 0.9.8f */ + SSL_CTX_set_options(context, SSL_OP_NO_TICKET); +#endif + + /* disallow SSL session caching, too */ + SSL_CTX_set_session_cache_mode(context, SSL_SESS_CACHE_OFF); + +// /* set up ephemeral DH and ECDH keys */ +// if (!initialize_dh(context, isServerStart)) +// goto error; +// if (!initialize_ecdh(context, isServerStart)) +// goto error; + +// /* set up the allowed cipher list */ +// if (SSL_CTX_set_cipher_list(context, SSLCipherSuites) != 1) +// { +// ereport(FATAL, +// (errcode(ERRCODE_CONFIG_FILE_ERROR), +// errmsg("could not set the cipher list (no valid ciphers available)"))); +// goto error; +// } + + /* Let server choose order */ +// if (SSLPreferServerCiphers) +// SSL_CTX_set_options(context, SSL_OP_CIPHER_SERVER_PREFERENCE); + + /* + * Load CA store, so we can verify client certificates if needed. + */ + if (pool_config->ssl_ca_cert) + { + if (SSL_CTX_load_verify_locations(context, pool_config->ssl_ca_cert, NULL) != 1 || + (root_cert_list = SSL_load_client_CA_file(pool_config->ssl_ca_cert)) == NULL) + { + ereport(FATAL, + (errmsg("could not load root certificate file \"%s\": %s", + pool_config->ssl_ca_cert, SSLerrmessage(ERR_get_error())))); + goto error; + } + /* + * Always ask for SSL client cert, but don't fail if it's not + * presented. We might fail such connections later, depending on what + * we find in pg_hba.conf. + */ + SSL_CTX_set_verify(context, + (SSL_VERIFY_PEER | + SSL_VERIFY_CLIENT_ONCE), + verify_cb); + + /* + * Tell OpenSSL to send the list of root certs we trust to clients in + * CertificateRequests. This lets a client with a keystore select the + * appropriate client certificate to send to us. + */ + SSL_CTX_set_client_CA_list(context, root_cert_list); + } + + /* + * Success! Replace any existing SSL_context. + */ + if (SSL_frontend_context) + SSL_CTX_free(SSL_frontend_context); + + SSL_frontend_context = context; + + /* + * Set flag to remember whether CA store has been loaded into SSL_context. + */ +// if (pool_config->ssl_ca_cert) +// ssl_loaded_verify_locations = true; +// else +// ssl_loaded_verify_locations = false; +// + return 0; + +error: + if (context) + SSL_CTX_free(context); + return -1; +} + #else /* USE_SSL: wrap / no-op ssl functionality if it's not available */ void pool_ssl_negotiate_serverclient(POOL_CONNECTION *cp) { @@ -363,6 +716,11 @@ int pool_ssl_write(POOL_CONNECTION *cp, const void *buf, int size) { return -1; /* never reached */ } +int SSL_ServerSide_init(void) +{ + return 0; +} + bool pool_ssl_pending(POOL_CONNECTION *cp) { return false;