This is an automated email from the ASF dual-hosted git repository. huor pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/hawq.git
commit 777072708525a6161e4f8114cc4894513e0c4bf9 Author: wcl14 <[email protected]> AuthorDate: Tue May 29 16:26:22 2018 +0800 HAWQ-1681. Support manage user in cloud --- src/backend/commands/user.c | 503 +++++++++++++++++++++++++++++++++++- src/backend/commands/variable.c | 12 +- src/backend/libpq/Makefile | 2 +- src/backend/libpq/auth.c | 69 +++++ src/backend/libpq/cloudrest.c | 446 ++++++++++++++++++++++++++++++++ src/backend/libpq/hba.c | 15 ++ src/backend/tcop/postgres.c | 1 + src/backend/utils/cache/lsyscache.c | 3 +- src/backend/utils/init/miscinit.c | 7 +- src/backend/utils/misc/guc.c | 24 ++ src/include/commands/user.h | 3 + src/include/libpq/hba.h | 4 +- src/include/utils/cloudrest.h | 88 +++++++ src/include/utils/guc.h | 6 + 14 files changed, 1167 insertions(+), 16 deletions(-) diff --git a/src/backend/commands/user.c b/src/backend/commands/user.c index f8fc301..d1da5a5 100644 --- a/src/backend/commands/user.c +++ b/src/backend/commands/user.c @@ -58,6 +58,7 @@ #include "utils/lsyscache.h" #include "executor/execdesc.h" +#include "utils/cloudrest.h" #include "utils/resscheduler.h" #include "utils/syscache.h" @@ -112,6 +113,7 @@ static void AddRoleDenials(const char *rolename, Oid roleid, static void DelRoleDenials(const char *rolename, Oid roleid, List *dropintervals); static bool resourceQueueIsBranch(Oid queueid); +static Oid CreateNoPrivligeRole(char *rolename); /* Check if current user has createrole privileges */ static bool @@ -121,6 +123,11 @@ have_createrole_privilege(void) cqContext *pcqCtx, cqc; HeapTuple utup; + if (pg_cloud_auth) + { + return pg_cloud_createrole; + } + /* Superusers can always do everything */ if (superuser()) return true; @@ -547,6 +554,52 @@ CreateRole(CreateRoleStmt *stmt) else new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; + if (pg_cloud_auth) + { + char *errormsg; + int ret = check_authentication_from_cloud(stmt->role, password, + &createrole, USER_SYNC, "create", &errormsg); + elog(INFO, "in CreateRole, ret=%d", ret); + if (ret) + { + /* + * role exists in cloud, create it in database but without any authorities + */ + if (ret == CLOUDSYNC_USEREXIST) + { + new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum( + false); + new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum( + false); + new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum( + false); + new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum( + false); + new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum( + false); + new_record[Anum_pg_authid_rolcreaterextgpfd - 1] = + BoolGetDatum(false); + new_record[Anum_pg_authid_rolcreaterexthttp - 1] = + BoolGetDatum(false); + new_record[Anum_pg_authid_rolcreatewextgpfd - 1] = + BoolGetDatum(false); + new_record[Anum_pg_authid_rolcreaterexthdfs - 1] = + BoolGetDatum(false); + new_record[Anum_pg_authid_rolcreatewexthdfs - 1] = + BoolGetDatum(false); + } + else + { + elog(ERROR, "%s", errormsg); + if (errormsg) + { + pfree(errormsg); + errormsg = NULL; + } + } + } + } + if (validUntil) new_record[Anum_pg_authid_rolvaliduntil - 1] = DirectFunctionCall3(timestamptz_in, @@ -1011,11 +1064,13 @@ AlterRole(AlterRoleStmt *stmt) tuple = caql_getnext(pcqCtx); - if (!HeapTupleIsValid(tuple)) { + if (!HeapTupleIsValid(tuple) + && !CheckUserExistOnCloud(pcqCtx, pg_authid_rel, stmt->role, &tuple, + true)) + { releaseResourceContextWithErrorReport(resourceid); ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", stmt->role))); + (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", stmt->role))); } roleid = HeapTupleGetOid(tuple); @@ -1095,6 +1150,24 @@ AlterRole(AlterRoleStmt *stmt) { new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole > 0); new_record_repl[Anum_pg_authid_rolcreaterole - 1] = true; + + if (pg_cloud_auth) + { + char *errormsg; + int ret; + ret = check_authentication_from_cloud(stmt->role, NULL, + &new_record[Anum_pg_authid_rolcreaterole - 1], USER_SYNC, + "alter", &errormsg); + elog(INFO, "in AlterRole, ret=%d", ret); + if (ret) + { + elog(ERROR, "%s", errormsg); + if (errormsg) { + pfree(errormsg); + errormsg = NULL; + } + } + } } if (createdb >= 0) @@ -1132,6 +1205,23 @@ AlterRole(AlterRoleStmt *stmt) CStringGetTextDatum(encrypted_password); } new_record_repl[Anum_pg_authid_rolpassword - 1] = true; + + if (pg_cloud_auth) + { + char *errormsg; + int ret; + ret = check_authentication_from_cloud(stmt->role, password, NULL, + USER_SYNC, "alter", &errormsg); + elog(INFO, "in AlterRole, ret=%d", ret); + if (ret) + { + elog(ERROR, "%s", errormsg); + if (errormsg) { + pfree(errormsg); + errormsg = NULL; + } + } + } } /* unset password */ @@ -1549,6 +1639,25 @@ DropRole(DropRoleStmt *stmt) cqContext cqc; cqContext *pcqCtx; + if (pg_cloud_auth) + { + char *errormsg; + int ret = check_authentication_from_cloud(role, NULL, NULL, + USER_SYNC, "drop", &errormsg); + if (ret) + { + elog(ERROR, "%s", errormsg); + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("%s", errormsg), + errOmitLocation(true))); + if (errormsg) { + pfree(errormsg); + errormsg = NULL; + } + } + } + pcqCtx = caql_beginscan( caql_addrel(cqclr(&cqc), pg_authid_rel), cql("SELECT * FROM pg_authid " @@ -1768,6 +1877,12 @@ RenameRole(const char *oldname, const char *newname) cqContext cqc2; cqContext *pcqCtx; + if (pg_cloud_auth) + { + ereport(ERROR, + (errcode(ERRCODE_CDB_FEATURE_NOT_YET), errmsg("Cannot support rename role name when using cloud auth yet") )); + } + rel = heap_open(AuthIdRelationId, RowExclusiveLock); dsc = RelationGetDescr(rel); @@ -2911,3 +3026,385 @@ DelRoleDenials(const char *rolename, Oid roleid, List *dropintervals) */ auth_time_file_update_needed(); } + +static Oid +CreateNoPrivligeRole(char *rolename) +{ + Relation pg_authid_rel; + HeapTuple tuple; + Datum new_record[Natts_pg_authid]; + bool new_record_nulls[Natts_pg_authid]; + Oid roleid; + ListCell *item; + ListCell *option; + char *password = NULL; /* user password */ + bool encrypt_password = Password_encryption; /* encrypt password? */ + char encrypted_password[MAX_PASSWD_HASH_LEN + 1]; + bool issuper = false; /* Make the user a superuser? */ + bool inherit = true; /* Auto inherit privileges? */ + bool createrole = false; /* Can this user create roles? */ + bool createdb = false; /* Can the user create databases? */ + bool canlogin = false; /* Can this user login? */ + bool createrextgpfd = false; /* Can create readable gpfdist exttab? */ + bool createrexthttp = false; /* Can create readable http exttab? */ + bool createwextgpfd = false; /* Can create writable gpfdist exttab? */ + bool createrexthdfs = false; /* Can create readable hdfs exttab? */ + bool createwexthdfs = false; /* Can create writable hdfs exttab? */ + int connlimit = -1; /* maximum connections allowed */ + List *addroleto = NIL; /* roles to make this a member of */ + List *rolemembers = NIL; /* roles to be members of this role */ + List *adminmembers = NIL; /* roles to be admins of this role */ + List *exttabcreate = NIL; /* external table create privileges being added */ + List *exttabnocreate = NIL; /* external table create privileges being removed */ + char *validUntil = NULL; /* time the login is valid until */ + char *resqueue = NULL; /* resource queue for this role */ + List *addintervals = NIL; /* list of time intervals for which login should be denied */ + cqContext cqc; + cqContext cqc2; + cqContext *pcqCtx; + Oid queueid = InvalidOid; + int res = FUNC_RETURN_OK; + static char errorbuf[1024] = ""; + + /* Create a new resource context to manipulate role in resource manager. */ + int resourceid = 0; + res = createNewResourceContext(&resourceid); + if (res != FUNC_RETURN_OK) { + Assert( res == COMM2RM_CLIENT_FULL_RESOURCECONTEXT ); + ereport(ERROR, + (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("Can not apply CREATE ROLE. " + "Because too many resource contexts were created."))); + } + + /* Here, using user oid is more convenient. */ + res = registerConnectionInRMByOID(resourceid, + BOOTSTRAP_SUPERUSERID, + errorbuf, + sizeof(errorbuf)); + if (res != FUNC_RETURN_OK) + { + releaseResourceContextWithErrorReport(resourceid); + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("%s", errorbuf))); + } + + /* + * Check the pg_authid relation to be certain the role doesn't already + * exist. + */ + pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); + + pcqCtx = + caql_beginscan( + caql_addrel(cqclr(&cqc), pg_authid_rel), + cql("INSERT INTO pg_authid ", + NULL)); + + if (caql_getcount( + caql_addrel(cqclr(&cqc2), pg_authid_rel), + cql("SELECT COUNT(*) FROM pg_authid " + " WHERE rolname = :1 ", + PointerGetDatum(rolename)))) { + unregisterConnectionInRMWithErrorReport(resourceid); + releaseResourceContextWithErrorReport(resourceid); + ereport(ERROR, + (errcode(ERRCODE_DUPLICATE_OBJECT), + errmsg("role \"%s\" already exists", + rolename))); + } + + /* + * Build a tuple to insert + */ + MemSet(new_record, 0, sizeof(new_record)); + MemSet(new_record_nulls, false, sizeof(new_record_nulls)); + + new_record[Anum_pg_authid_rolname - 1] = + DirectFunctionCall1(namein, CStringGetDatum(rolename)); + + new_record[Anum_pg_authid_rolsuper - 1] = BoolGetDatum(issuper); + new_record[Anum_pg_authid_rolinherit - 1] = BoolGetDatum(inherit); + new_record[Anum_pg_authid_rolcreaterole - 1] = BoolGetDatum(createrole); + new_record[Anum_pg_authid_rolcreatedb - 1] = BoolGetDatum(createdb); + /* superuser gets catupdate right by default */ + new_record[Anum_pg_authid_rolcatupdate - 1] = BoolGetDatum(issuper); + new_record[Anum_pg_authid_rolcanlogin - 1] = BoolGetDatum(canlogin); + new_record[Anum_pg_authid_rolconnlimit - 1] = Int32GetDatum(connlimit); + + /* Set the CREATE EXTERNAL TABLE permissions for this role */ + if (exttabcreate || exttabnocreate) + SetCreateExtTableForRole(exttabcreate, exttabnocreate, &createrextgpfd, + &createrexthttp, &createwextgpfd, + &createrexthdfs, &createwexthdfs); + + new_record[Anum_pg_authid_rolcreaterextgpfd - 1] = BoolGetDatum(createrextgpfd); + new_record[Anum_pg_authid_rolcreaterexthttp - 1] = BoolGetDatum(createrexthttp); + new_record[Anum_pg_authid_rolcreatewextgpfd - 1] = BoolGetDatum(createwextgpfd); + new_record[Anum_pg_authid_rolcreaterexthdfs - 1] = BoolGetDatum(createrexthdfs); + new_record[Anum_pg_authid_rolcreatewexthdfs - 1] = BoolGetDatum(createwexthdfs); + + if (password) + { + if (!encrypt_password || isHashedPasswd(password)) + new_record[Anum_pg_authid_rolpassword - 1] = + CStringGetTextDatum(password); + else + { + if (!hash_password(password, rolename, strlen(rolename), + encrypted_password)) + { + elog(ERROR, "password encryption failed"); + } + + new_record[Anum_pg_authid_rolpassword - 1] = + CStringGetTextDatum(encrypted_password); + } + } + else + new_record_nulls[Anum_pg_authid_rolpassword - 1] = true; + + if (validUntil) + new_record[Anum_pg_authid_rolvaliduntil - 1] = + DirectFunctionCall3(timestamptz_in, + CStringGetDatum(validUntil), + ObjectIdGetDatum(InvalidOid), + Int32GetDatum(-1)); + + else + new_record_nulls[Anum_pg_authid_rolvaliduntil - 1] = true; + + resqueue = pstrdup(GP_DEFAULT_RESOURCE_QUEUE_NAME); + if (resqueue) + { + if (strcmp(resqueue, "none") == 0) + { + unregisterConnectionInRMWithErrorReport(resourceid); + releaseResourceContextWithErrorReport(resourceid); + ereport(ERROR, + (errcode(ERRCODE_RESERVED_NAME), + errmsg("resource queue name \"%s\" is reserved", + resqueue), errOmitLocation(true))); + } + + queueid = GetResQueueIdForName(resqueue); + if (queueid == InvalidOid) + { + unregisterConnectionInRMWithErrorReport(resourceid); + releaseResourceContextWithErrorReport(resourceid); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("resource queue \"%s\" does not exist", + resqueue), errOmitLocation(true))); + } + + if(resourceQueueIsBranch(queueid)) + { + unregisterConnectionInRMWithErrorReport(resourceid); + releaseResourceContextWithErrorReport(resourceid); + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("cannot assign non-leaf resource queue \"%s\" to role", + resqueue), errOmitLocation(true))); + } + + new_record[Anum_pg_authid_rolresqueue - 1] = + ObjectIdGetDatum(queueid); + } + else + new_record_nulls[Anum_pg_authid_rolresqueue - 1] = true; + + new_record_nulls[Anum_pg_authid_rolconfig - 1] = true; + + tuple = caql_form_tuple(pcqCtx, new_record, new_record_nulls); + + + /* + * Insert new record in the pg_authid table + */ + roleid = caql_insert(pcqCtx, tuple); /* implicit update of index as well */ + + /* + * send RPC: notify RM to update + */ + if (resqueue && queueid != InvalidOid) + { + res = manipulateRoleForResourceQueue(resourceid, + roleid, + queueid, + MANIPULATE_ROLE_RESQUEUE_CREATE, + issuper, + rolename, + errorbuf, + sizeof(errorbuf)); + } + + /* We always unregister connection. */ + unregisterConnectionInRMWithErrorReport(resourceid); + + /* We always release resource context. */ + releaseResourceContextWithErrorReport(resourceid); + + if (resqueue && queueid != InvalidOid) + { + if ( res != FUNC_RETURN_OK ) + { + ereport(ERROR, + (errcode(IS_TO_RM_RPC_ERROR(res) ? + ERRCODE_INTERNAL_ERROR : + ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("cannot apply CREATE ROLE because of %s", errorbuf))); + } + } + + /* + * Advance command counter so we can see new record; else tests in + * AddRoleMems may fail. + */ + if (addroleto || adminmembers || rolemembers) + CommandCounterIncrement(); + + /* + * Add the new role to the specified existing roles. + */ + foreach(item, addroleto) + { + char *oldrolename = strVal(lfirst(item)); + Oid oldroleid = get_roleid_checked(oldrolename); + + AddRoleMems(oldrolename, oldroleid, + list_make1(makeString(rolename)), + list_make1_oid(roleid), + BOOTSTRAP_SUPERUSERID, false); + } + + /* + * Add the specified members to this new role. adminmembers get the admin + * option, rolemembers don't. + */ + AddRoleMems(rolename, roleid, + adminmembers, roleNamesToIds(adminmembers), + BOOTSTRAP_SUPERUSERID, true); + AddRoleMems(rolename, roleid, + rolemembers, roleNamesToIds(rolemembers), + BOOTSTRAP_SUPERUSERID, false); + + /* + * Populate pg_auth_time_constraint with intervals for which this + * particular role should be denied access. + */ + if (addintervals) + { + if (issuper) + ereport(ERROR, + (errmsg("cannot create superuser with DENY rules"))); + AddRoleDenials(rolename, roleid, addintervals); + } + + /* + * Close pg_authid, but keep lock till commit (this is important to + * prevent any risk of deadlock failure while updating flat file) + */ + caql_endscan(pcqCtx); + heap_close(pg_authid_rel, NoLock); + CommitTransaction(); + StartTransaction(); + pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); + pcqCtx = + caql_beginscan( + caql_addrel(cqclr(&cqc), pg_authid_rel), + cql("SELECT * FROM pg_authid " + " WHERE oid = :1 ", + PointerGetDatum(BOOTSTRAP_SUPERUSERID))); + tuple = caql_getnext(pcqCtx); + caql_endscan(pcqCtx); + heap_close(pg_authid_rel, NoLock); + + /* + * Set flag to update flat auth file at commit. + */ + auth_file_update_needed(); + + if (Gp_role == GP_ROLE_DISPATCH) + { + /* GPSQL: no dispatch to segments */ + /* CdbDispatchUtilityStatement((Node *) stmt, "CreateRole"); */ + + /* MPP-6929: metadata tracking */ + MetaTrackAddObject(AuthIdRelationId, + roleid, + BOOTSTRAP_SUPERUSERID, + "CREATE", "ROLE" + ); + } + + return roleid; +} + +bool +CheckUserExistOnCloudSimple(char *rolename, Oid *roleid) +{ + if (!pg_cloud_auth) + return false; + + char *errormsg; + int ret = check_authentication_from_cloud(rolename, NULL, NULL, USER_EXIST, + "", &errormsg); + if (ret) + { + ereport(LOG, + (errmsg("%s", errormsg))); + if (errormsg) { + pfree(errormsg); + errormsg = NULL; + } + return false; + } + HeapTuple tuple; + *roleid = CreateNoPrivligeRole(rolename); + return true; +} + +bool CheckUserExistOnCloud(cqContext *pcqCtx, Relation pg_authid_rel, + char *rolename, HeapTuple *tuple, bool forUpdate) +{ + if (!pg_cloud_auth) + return false; + + char *errormsg; + int ret = check_authentication_from_cloud(rolename, NULL, NULL, USER_EXIST, + "", &errormsg); + if (ret) + { + ereport(LOG, (errmsg("%s", errormsg))); + if (errormsg) + { + pfree(errormsg); + errormsg = NULL; + } + return false; + } + caql_endscan(pcqCtx); + if (pg_authid_rel) + { + heap_close(pg_authid_rel, NoLock); + } + Oid roleid = CreateNoPrivligeRole(rolename); + cqContext cqc; + if (forUpdate) + { + pg_authid_rel = heap_open(AuthIdRelationId, RowExclusiveLock); + pcqCtx = caql_beginscan(caql_addrel(cqclr(&cqc), pg_authid_rel), + cql("SELECT * FROM pg_authid " + " WHERE oid = :1 " + " FOR UPDATE ", ObjectIdGetDatum(roleid))); + } + else + { + pcqCtx = caql_beginscan( + NULL, cql("SELECT * FROM pg_authid " + " WHERE oid = :1 ", ObjectIdGetDatum(roleid))); + } + + *tuple = caql_getnext(pcqCtx); + return true; +} diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c index b431228..6c801ee 100644 --- a/src/backend/commands/variable.c +++ b/src/backend/commands/variable.c @@ -776,12 +776,12 @@ assign_session_authorization(const char *value, bool doit, GucSource source) PointerGetDatum((char *) value))); roleTup = caql_getnext(pcqCtx); - if (!HeapTupleIsValid(roleTup)) + if (!HeapTupleIsValid(roleTup) + && !CheckUserExistOnCloud(pcqCtx, NULL, value, &roleTup, false)) { if (source >= PGC_S_INTERACTIVE) ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", value))); + (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", value))); return NULL; } @@ -922,12 +922,12 @@ assign_role(const char *value, bool doit, GucSource source) PointerGetDatum((char *) value))); roleTup = caql_getnext(pcqCtx); - if (!HeapTupleIsValid(roleTup)) + if (!HeapTupleIsValid(roleTup) + && !CheckUserExistOnCloud(pcqCtx, NULL, value, &roleTup, false)) { if (source >= PGC_S_INTERACTIVE) ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("role \"%s\" does not exist", value))); + (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", value))); return NULL; } diff --git a/src/backend/libpq/Makefile b/src/backend/libpq/Makefile index 16124c7..a55f694 100644 --- a/src/backend/libpq/Makefile +++ b/src/backend/libpq/Makefile @@ -15,6 +15,6 @@ include $(top_builddir)/src/Makefile.global # be-fsstubs is here for historical reasons, probably belongs elsewhere OBJS = be-fsstubs.o be-secure.o auth.o crypt.o hba.o ip.o md5.o pqcomm.o \ - pqformat.o pqsignal.o sha2.o pg_sha2.o rangerrest.o + pqformat.o pqsignal.o sha2.o pg_sha2.o rangerrest.o cloudrest.o include $(top_srcdir)/src/backend/common.mk diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c index fbd2b32..1e0e888 100644 --- a/src/backend/libpq/auth.c +++ b/src/backend/libpq/auth.c @@ -40,6 +40,7 @@ #include "pgtime.h" #include "postmaster/postmaster.h" #include "utils/builtins.h" +#include "utils/cloudrest.h" #include "utils/datetime.h" #include "utils/guc.h" #include "utils/timestamp.h" @@ -213,6 +214,12 @@ static int pg_SSPI_recvauth(Port *port); #endif static int CheckRADIUSAuth(Port *port); +/*---------------------------------------------------------------- + * Cloud Authentication + *---------------------------------------------------------------- + */ +static int CheckCloudAuth(Port *port); + /* * Maximum accepted size of GSS and SSPI authentication tokens. * @@ -313,6 +320,9 @@ auth_failed(Port *port, int status) case uaRADIUS: errstr = gettext_noop("RADIUS authentication failed for user \"%s\""); break; + case uaCloud: + errstr = gettext_noop("Cloud authentication failed for user \"%s\""); + break; default: errstr = gettext_noop("authentication failed for user \"%s\": invalid authentication method"); break; @@ -711,6 +721,9 @@ ClientAuthentication(Port *port) case uaRADIUS: status = CheckRADIUSAuth(port); break; + case uaCloud: + status = CheckCloudAuth(port); + break; case uaTrust: status = STATUS_OK; break; @@ -2696,6 +2709,62 @@ CheckCertAuth(Port *port) } #endif +/* + * Called when we have sent an authorization request for a password. + * Get the response and check it. + */ +static int +CheckCloudAuth(Port *port) +{ + char *passwd; + int result; + + pg_cloud_auth = true; + + elog(LOG, "in CheckCloudAuth, port->hba->cloudserver=%s, pg_cloud_clustername=%s", port->hba->cloudserver, pg_cloud_clustername); + if (!port->hba->cloudserver || port->hba->cloudserver[0] == '\0') + { + ereport(LOG, + (errmsg("cloud server not specified"))); + return STATUS_ERROR; + } + + sendAuthRequest(port, AUTH_REQ_PASSWORD); + + passwd = recv_password_packet(port); + if (passwd == NULL) + return STATUS_EOF; /* client wouldn't send password */ + + if (strlen(passwd) == 0) + { + ereport(LOG, + (errmsg("empty password returned by client"))); + return STATUS_ERROR; + } + + elog(LOG, "in CheckCloudAuth, before init_cloud_curl"); + init_cloud_curl(); + + char *errormsg; + elog(LOG, "in CheckCloudAuth, before check_authentication_from_cloud"); + result = check_authentication_from_cloud(port->user_name, passwd, NULL, + AUTHENTICATION_CHECK, "", &errormsg); + if (result) + { + ereport(LOG, + (errmsg("%s", errormsg))); + if (errormsg) { + pfree(errormsg); + errormsg = NULL; + } + } + + pfree(passwd); + + return result; + +} + /*---------------------------------------------------------------- * RADIUS authentication diff --git a/src/backend/libpq/cloudrest.c b/src/backend/libpq/cloudrest.c new file mode 100644 index 0000000..4ef0412 --- /dev/null +++ b/src/backend/libpq/cloudrest.c @@ -0,0 +1,446 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2016, Oushu Inc. +// All rights reserved. +// +// Author: +/////////////////////////////////////////////////////////////////////////////// + +#include "utils/cloudrest.h" + +#include <string.h> + +#include "utils/elog.h" + +char *pg_cloud_server = NULL; +char *pg_cloud_token = NULL; +bool pg_cloud_createrole; +bool pg_cloud_auth = false; + +static void finalize_cloud_curl(); + +static json_object *create_cloud_authentication_request_json(char *username, char *password) +{ + json_object *jrequest = json_object_new_object(); + + json_object *jusername = json_object_new_string(username); + json_object_object_add(jrequest, "username", jusername); + json_object *jpassword = json_object_new_string(password); + json_object_object_add(jrequest, "password", jpassword); + json_object *jclustername = json_object_new_string(pg_cloud_clustername); + json_object_object_add(jrequest, "clustername", jclustername); + + return jrequest; +} + +static json_object *create_cloud_usersync_request_json(char *username, char *password, bool *createuser, char *action) +{ + json_object *jrequest = json_object_new_object(); + + json_object *jaction = json_object_new_string(action); + json_object_object_add(jrequest, "action", jaction); + json_object *jtoken = json_object_new_string(pg_cloud_token); + json_object_object_add(jrequest, "token", jtoken); + json_object *jusername = json_object_new_string(username); + json_object_object_add(jrequest, "username", jusername); + if (password) + { + json_object *jpassword = json_object_new_string(password); + json_object_object_add(jrequest, "password", jpassword); + } + json_object *jclustername = json_object_new_string(pg_cloud_clustername); + json_object_object_add(jrequest, "clustername", jclustername); + if (createuser) + { + json_object *jcreateuser = json_object_new_boolean(*createuser); + json_object_object_add(jrequest, "cancreateuser", jcreateuser); + } + + return jrequest; +} + +static json_object *create_cloud_userexist_request_json(char *username) +{ + json_object *jrequest = json_object_new_object(); + + json_object *jusername = json_object_new_string(username); + json_object_object_add(jrequest, "username", jusername); + json_object *jclustername = json_object_new_string(pg_cloud_clustername); + json_object_object_add(jrequest, "clustername", jclustername); + + return jrequest; +} + +static size_t write_callback(char *contents, size_t size, size_t nitems, + void *userp) +{ + size_t realsize = size * nitems; + CURL_HANDLE curl = (CURL_HANDLE) userp; + Assert(curl != NULL); + + elog(DEBUG3, "cloud restful response size is %d. response buffer size is %d.", curl->response.response_size, curl->response.buffer_size); + int original_size = curl->response.buffer_size; + while(curl->response.response_size + realsize >= curl->response.buffer_size) + { + /* double the buffer size if the buffer is not enough.*/ + curl->response.buffer_size = curl->response.buffer_size * 2; + } + if(original_size < curl->response.buffer_size) + { + /* repalloc is not same as realloc, repalloc's first parameter cannot be NULL */ + curl->response.buffer = repalloc(curl->response.buffer, curl->response.buffer_size); + } + elog(DEBUG3, "cloud restful response size is %d. response buffer size is %d.", curl->response.response_size, curl->response.buffer_size); + if (curl->response.buffer == NULL) + { + /* allocate memory failed. probably out of memory */ + elog(WARNING, "cannot allocate memory for cloud response"); + return 0; + } + memcpy(curl->response.buffer + curl->response.response_size, contents, realsize); + curl->response.response_size += realsize; + curl->response.buffer[curl->response.response_size] = '\0'; + elog(DEBUG3, "read from cloud restful response: %s", curl->response.buffer); + return realsize; +} + +/** + * @return 0 curl success; -1 curl failed + */ +static int call_cloud_rest(CURL_HANDLE curl_handle, const char* request, char *action) +{ + int ret = -1; + CURLcode res; + Assert(request != NULL); + + /* + * Re-initializes all options previously set on a specified CURL handle + * to the default values. This puts back the handle to the same state as + * it was in when it was just created with curl_easy_init.It does not + * change the following information kept in the handle: live connections, + * the Session ID cache, the DNS cache, the cookies and shares. + */ + curl_easy_reset(curl_handle->curl_handle); + /* timeout: hard-coded temporarily and maybe should be a guc in future */ + curl_easy_setopt(curl_handle->curl_handle, CURLOPT_TIMEOUT, 30L); + + /* specify URL to get */ + StringInfoData tname; + initStringInfo(&tname); + appendStringInfo(&tname, "%s", pg_cloud_server); + appendStringInfo(&tname, "/"); + appendStringInfo(&tname, "%s", action); + curl_easy_setopt(curl_handle->curl_handle, CURLOPT_URL, tname.data); + elog(INFO, "in call_cloud_rest: %s", tname.data); + pfree(tname.data); + + struct curl_slist *headers = NULL; + headers = curl_slist_append(headers, "Content-Type:application/json"); + if (pg_cloud_token) { + char buf[512]; + memset(buf, 0, sizeof(buf)); + sprintf(buf, "token:%s", pg_cloud_token); + elog(INFO, "in call_cloud_rest: %s", buf); + headers = curl_slist_append(headers, buf); + } + curl_easy_setopt(curl_handle->curl_handle, CURLOPT_HTTPHEADER, headers); + + curl_easy_setopt(curl_handle->curl_handle, CURLOPT_POSTFIELDS, request); + /* send all data to this function */ + curl_easy_setopt(curl_handle->curl_handle, CURLOPT_WRITEFUNCTION, + write_callback); + curl_easy_setopt(curl_handle->curl_handle, CURLOPT_WRITEDATA, + (void * )curl_handle); + + res = curl_easy_perform(curl_handle->curl_handle); + /* check for errors */ + if (res != CURLE_OK) + { + elog(ERROR, "cloud server from %s/%s is unavailable : %s.\n", + pg_cloud_server, action, curl_easy_strerror(res)); + } + else + { + ret = 0; + elog(DEBUG3, "retrieved %d bytes data from cloud restful response.", + curl_handle->response.response_size); + } + + return ret; +} + +static int parse_cloud_auth_response(char* buffer, int *result, char **errormsg) +{ + if (buffer == NULL || strlen(buffer) == 0) + return -1; + + elog(DEBUG3, "parse cloud restful response content : %s", buffer); + + struct json_object *response = json_tokener_parse(buffer); + if (response == NULL) + { + elog(WARNING, "failed to parse json tokener."); + return -1; + } + + struct json_object *jtoken = NULL; + if (!json_object_object_get_ex(response, "token", &jtoken)) + { + elog(WARNING, "failed to get json \"token\" field."); + return -1; + } + char *token = json_object_get_string(jtoken); + size_t len = strlen(token); + MemoryContext old; + old = MemoryContextSwitchTo(TopMemoryContext); + pg_cloud_token = (char *)palloc0(len + 1); + memcpy(pg_cloud_token, token, len); + MemoryContextSwitchTo(old); + elog(INFO, "in parse_cloud_auth_response, token(%p): %s", pg_cloud_token, pg_cloud_token); + + struct json_object *jcreaterole = NULL; + if (!json_object_object_get_ex(response, "cancreateuser", &jcreaterole)) + { + elog(WARNING, "failed to get json \"cancreateuser\" field."); + return -1; + } + pg_cloud_createrole = json_object_get_boolean(jcreaterole); + elog(INFO, "pg_cloud_createrole=%d", pg_cloud_createrole); + + struct json_object *jresult = NULL; + if (!json_object_object_get_ex(response, "result", &jresult)) + { + elog(WARNING, "failed to get json \"result\" field."); + return -1; + } + + json_bool ok = json_object_get_int(jresult); + if (ok == 1) + { + *result = CLOUDCHECK_OK; + } + else + { + struct json_object *jerror = NULL; + if (!json_object_object_get_ex(response, "error", &jerror)) + { + elog(WARNING, "failed to get json \"token\" field."); + return -1; + } + char *err = json_object_get_string(jerror); + size_t len = strlen(err); + *errormsg = (char *)palloc0(len + 1); + memcpy(*errormsg, err, len); + (*errormsg)[len] = '\0'; + *result = CLOUDCHECK_NO_PRIV; + elog(INFO, "errmsg=%s, size=%d", *errormsg, strlen(*errormsg)); + } + + return 0; +} + +static int parse_cloud_sync_response(char* buffer, int *result, char **errormsg) +{ + if (buffer == NULL || strlen(buffer) == 0) + return -1; + + elog(DEBUG3, "parse cloud restful response content : %s", buffer); + + struct json_object *response = json_tokener_parse(buffer); + if (response == NULL) + { + elog(WARNING, "failed to parse json tokener."); + return -1; + } + + struct json_object *jresult = NULL; + if (!json_object_object_get_ex(response, "result", &jresult)) + { + elog(WARNING, "failed to get json \"result\" field."); + return -1; + } + + int ok = json_object_get_boolean(jresult); + elog(INFO, "in parse_cloud_sync_response, ret=%d", ok); + if (ok == 0) + { + *result = CLOUDSYNC_OK; + } + else + { + struct json_object *jerror = NULL; + if (!json_object_object_get_ex(response, "error", &jerror)) + { + elog(WARNING, "failed to get json \"token\" field."); + return -1; + } + char *err = json_object_get_string(jerror); + size_t len = strlen(err); + *errormsg = (char *)palloc0(len + 1); + memcpy(*errormsg, err, len); + (*errormsg)[len] = '\0'; + if (ok == 1) + *result = CLOUDSYNC_USEREXIST; + else + *result = CLOUDSYNC_FAIL; + } + + return 0; +} + +static int parse_cloud_exist_response(char* buffer, int *result, char **errormsg) +{ + if (buffer == NULL || strlen(buffer) == 0) + return -1; + + elog(DEBUG3, "parse cloud restful response content : %s", buffer); + + struct json_object *response = json_tokener_parse(buffer); + if (response == NULL) + { + elog(WARNING, "failed to parse json tokener."); + return -1; + } + + struct json_object *jresult = NULL; + if (!json_object_object_get_ex(response, "result", &jresult)) + { + elog(WARNING, "failed to get json \"result\" field."); + return -1; + } + + json_bool ok = json_object_get_boolean(jresult); + if (ok == 1) + { + *result = CLOUDUSER_EXIST; + } + else + { + *result = CLOUDUSER_NOTEXIST; + } + + return 0; +} + +void init_cloud_curl() { + memset(&curl_context_cloud, 0, sizeof(curl_context_t)); + curl_global_init(CURL_GLOBAL_ALL); + /* init the curl session */ + curl_context_cloud.curl_handle = curl_easy_init(); + if (curl_context_cloud.curl_handle == NULL) { + /* cleanup curl stuff */ + /* no need to cleanup curl_handle since it's null. just cleanup curl global.*/ + curl_global_cleanup(); + elog(ERROR, "initialize global curl context failed."); + } + curl_context_cloud.hasInited = true; + curl_context_cloud.response.buffer = palloc0(CURL_RES_BUFFER_SIZE); + curl_context_cloud.response.buffer_size = CURL_RES_BUFFER_SIZE; + elog(DEBUG3, "initialize global curl context for privileges check."); + on_proc_exit(finalize_cloud_curl, 0); +} + +void finalize_cloud_curl() { + if (curl_context_cloud.response.buffer != NULL) { + pfree(curl_context_cloud.response.buffer); + } + /* cleanup curl stuff */ + if (curl_context_cloud.curl_handle) { + curl_easy_cleanup(curl_context_cloud.curl_handle); + } + /* we're done with libcurl, so clean it up */ + curl_global_cleanup(); + curl_context_cloud.hasInited = false; + elog(DEBUG3, "finalize the global struct for curl handle context."); +} + +int check_authentication_from_cloud(char *username, char *password, + bool *createuser, CouldAuthAction authAction, char * action, + char **errormsg) +{ + json_object* jrequest; + switch (authAction) + { + case AUTHENTICATION_CHECK: + jrequest = create_cloud_authentication_request_json(username, password); + break; + case USER_SYNC: + jrequest = create_cloud_usersync_request_json(username, password, + createuser, action); + break; + case USER_EXIST: + jrequest = create_cloud_userexist_request_json(username); + break; + default: + elog(ERROR, "Invalid cloud authentication action:%d", authAction); + } + Assert(jrequest != NULL); + + const char *request = json_object_to_json_string(jrequest); + Assert(request != NULL); + elog( + DEBUG3, "send json request to cloud : %s", request); + + /* call GET method to send request*/ + Assert(curl_context_cloud.hasInited); + switch (authAction) + { + case AUTHENTICATION_CHECK: + if (call_cloud_rest(&curl_context_cloud, request, "cloudauthenticate") < 0) + { + return -1; + } + break; + case USER_SYNC: + if (call_cloud_rest(&curl_context_cloud, request, "syncuser") < 0) + { + return -1; + } + break; + case USER_EXIST: + if (call_cloud_rest(&curl_context_cloud, request, "userexist") < 0) + { + return -1; + } + break; + default: + elog(ERROR, "Invalid cloud authentication action:%d", authAction); + } + + /* free the JSON object */ + json_object_put(jrequest); + + /* parse the JSON-format result */ + int result, + ret; + switch (authAction) + { + case AUTHENTICATION_CHECK: + ret = parse_cloud_auth_response(curl_context_cloud.response.buffer, + &result, errormsg); + break; + case USER_SYNC: + ret = parse_cloud_sync_response(curl_context_cloud.response.buffer, + &result, errormsg); + break; + case USER_EXIST: + ret = parse_cloud_exist_response(curl_context_cloud.response.buffer, + &result, errormsg); + break; + default: + elog(ERROR, "Invalid cloud authentication action:%d", authAction); + } + if (ret < 0) + { + elog( + ERROR, "parse cloud response failed, cloud response content is %s", + curl_context_cloud.response.buffer == NULL? "empty.":curl_context_cloud.response.buffer); + } + if (curl_context_cloud.response.buffer != NULL) + { + /* reset response size to reuse the buffer. */ + curl_context_cloud.response.response_size = 0; + } + + elog(INFO, "in check_authentication_from_cloud: ret=%d", result); + return result; +} diff --git a/src/backend/libpq/hba.c b/src/backend/libpq/hba.c index d430335..6f89133 100644 --- a/src/backend/libpq/hba.c +++ b/src/backend/libpq/hba.c @@ -25,12 +25,14 @@ #include <arpa/inet.h> #include <unistd.h> +#include "libpq/auth.h" #include "libpq/ip.h" #include "libpq/libpq.h" #include "regex/regex.h" #include "storage/fd.h" #include "utils/flatfiles.h" #include "utils/acl.h" +#include "utils/cloudrest.h" #include "utils/guc.h" #include "utils/lsyscache.h" #include "utils/memutils.h" @@ -1087,6 +1089,8 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) #endif else if (strcmp(token, "radius") == 0) parsedline->auth_method = uaRADIUS; + else if (strcmp(token, "cloud") == 0) + parsedline->auth_method = uaCloud; else { ereport(LOG, @@ -1373,6 +1377,17 @@ parse_hba_line(List *line, int line_num, HbaLine *parsedline) REQUIRE_AUTH_OPTION(uaRADIUS, "radiusidentifier", "radius"); parsedline->radiusidentifier = pstrdup(c); } + else if (strcmp(token, "cloudserver") == 0) + { + REQUIRE_AUTH_OPTION(uaCloud, "cloudserver", "cloud"); + parsedline->cloudserver = pstrdup(c); + size_t len = strlen(parsedline->cloudserver); + MemoryContext old; + old = MemoryContextSwitchTo(TopMemoryContext); + pg_cloud_server = (char *)palloc0(len + 1); + memcpy(pg_cloud_server, parsedline->cloudserver, len); + MemoryContextSwitchTo(old); + } else { ereport(LOG, diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 4928ce2..cf0123e 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -137,6 +137,7 @@ extern char *savedSeqServerHost; extern int savedSeqServerPort; struct curl_context_t curl_context_ranger; +struct curl_context_t curl_context_cloud; /* ---------------- * global variables * ---------------- diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c index 2bd929f..d2a09c3 100644 --- a/src/backend/utils/cache/lsyscache.c +++ b/src/backend/utils/cache/lsyscache.c @@ -54,6 +54,7 @@ #include "cdb/cdbpartition.h" #include "commands/tablecmds.h" #include "commands/trigger.h" +#include "commands/user.h" #include "miscadmin.h" #include "nodes/makefuncs.h" #include "parser/parse_clause.h" /* for sort_op_can_sort() */ @@ -3366,7 +3367,7 @@ get_roleid_checked(const char *rolname) Oid roleid; roleid = get_roleid(rolname); - if (!OidIsValid(roleid)) + if (!OidIsValid(roleid) && !(CheckUserExistOnCloudSimple(rolname, &roleid))) ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), errmsg("role \"%s\" does not exist", rolname), diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c index 640cafa..c5384f9 100644 --- a/src/backend/utils/init/miscinit.c +++ b/src/backend/utils/init/miscinit.c @@ -444,11 +444,10 @@ InitializeSessionUserId(const char *rolename) roleTup = caql_getnext(pcqCtx); - if (!HeapTupleIsValid(roleTup)) + if (!HeapTupleIsValid(roleTup) + && !CheckUserExistOnCloud(pcqCtx, NULL, rolename, &roleTup, false)) ereport(FATAL, - (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), - errmsg("role \"%s\" does not exist", rolename), - errOmitLocation(true), errSendAlert(false))); + (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("role \"%s\" does not exist", rolename), errOmitLocation(true), errSendAlert(false))); rform = (Form_pg_authid) GETSTRUCT(roleTup); roleid = HeapTupleGetOid(roleTup); diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 6577b4c..0cf51df 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -790,6 +790,21 @@ char *acl_type; int rps_addr_port; int rps_check_local_interval; +char *rps_addr_host; +char *rps_addr_suffix; +int rps_addr_port; + +char *pg_cloud_clustername = NULL; + +/*auto-switch service*/ +bool enable_master_auto_ha = false; + +/* + * zookeeper_server hostlist + * "host1:port1,host2:port2,...,hostx:postx" + */ +char *ha_zookeeper_quorum = "localhost:2181"; + /* * Displayable names for context types (enum GucContext) * @@ -8228,6 +8243,15 @@ static struct config_string ConfigureNamesString[] = }, { + {"clusterName", PGC_POSTMASTER, PRESET_OPTIONS, + gettext_noop("Corresponding identifier for this hawq cluster in oushu cloud system."), + NULL + }, + &pg_cloud_clustername, + "", NULL, NULL + }, + + { {"hawq_standby_address_host", PGC_POSTMASTER, PRESET_OPTIONS, gettext_noop("standby server address hostname"), NULL diff --git a/src/include/commands/user.h b/src/include/commands/user.h index 01fb92c..c71b6a7 100644 --- a/src/include/commands/user.h +++ b/src/include/commands/user.h @@ -11,6 +11,7 @@ #ifndef USER_H #define USER_H +#include "catalog/catquery.h" #include "nodes/parsenodes.h" @@ -22,5 +23,7 @@ extern void GrantRole(GrantRoleStmt *stmt); extern void RenameRole(const char *oldname, const char *newname); extern void DropOwnedObjects(DropOwnedStmt *stmt); extern void ReassignOwnedObjects(ReassignOwnedStmt *stmt); +extern bool CheckUserExistOnCloudSimple(char *rolename, Oid *roleid); +extern bool CheckUserExistOnCloud(cqContext *pcqCtx, Relation pg_authid_rel, char *rolename, HeapTuple *tuple, bool forUpdate); #endif /* USER_H */ diff --git a/src/include/libpq/hba.h b/src/include/libpq/hba.h index bc81f9d..9779c62 100644 --- a/src/include/libpq/hba.h +++ b/src/include/libpq/hba.h @@ -29,7 +29,8 @@ typedef enum UserAuth uaPAM, uaLDAP, uaCert, - uaRADIUS + uaRADIUS, + uaCloud } UserAuth; typedef enum IPCompareMethod @@ -77,6 +78,7 @@ typedef struct char *radiussecret; char *radiusidentifier; int radiusport; + char *cloudserver; } HbaLine; /* kluge to avoid including libpq/libpq-be.h here */ diff --git a/src/include/utils/cloudrest.h b/src/include/utils/cloudrest.h new file mode 100644 index 0000000..e4e3cc8 --- /dev/null +++ b/src/include/utils/cloudrest.h @@ -0,0 +1,88 @@ +/////////////////////////////////////////////////////////////////////////////// +// Copyright 2016, Oushu Inc. +// All rights reserved. +// +// Author: +/////////////////////////////////////////////////////////////////////////////// +#ifndef SRC_INCLUDE_UTILS_CLOUDREST_H_ +#define SRC_INCLUDE_UTILS_CLOUDREST_H_ + +#include <json-c/json.h> +#include <curl/curl.h> + +#include "postgres.h" +#include "utils/guc.h" +#include "miscadmin.h" +#include "libpq/auth.h" +#include "libpq/libpq-be.h" +#include "tcop/tcopprot.h" + +#define HOST_BUFFER_SIZE 1025 +#define CURL_RES_BUFFER_SIZE 1024 + +typedef enum +{ + AUTHENTICATION_CHECK = 0, + USER_SYNC, + USER_EXIST +} CouldAuthAction; + +typedef enum +{ + CLOUDCHECK_OK = 0, + CLOUDCHECK_NO_PRIV, + CLOUDCHECK_UNKNOWN +} CouldAuthResult; + +typedef enum +{ + CLOUDSYNC_OK = 0, + CLOUDSYNC_USEREXIST, + CLOUDSYNC_FAIL, + CLOUDSYNC_UNKNOWN +} CouldSyncResult; + +typedef enum +{ + CLOUDUSER_EXIST = 0, + CLOUDUSER_NOTEXIST, +} CouldExistResult; + +/* + * Internal buffer for libcurl context + */ +typedef struct curl_context_t +{ + CURL* curl_handle; + + char curl_error_buffer[CURL_ERROR_SIZE]; + + int curl_still_running; + + struct + { + char* buffer; + int response_size; + int buffer_size; + } response; + + char* last_http_reponse; + + bool hasInited; +} curl_context_t; + +typedef curl_context_t* CURL_HANDLE; + +extern struct curl_context_t curl_context_cloud; + +extern void init_cloud_curl(); +extern int check_authentication_from_cloud(char *username, char *password, + bool *createuser, CouldAuthAction authAction, char * action, + char **errmsg); + +extern char *pg_cloud_server; +extern char *pg_cloud_token; +extern bool pg_cloud_createrole; +extern bool pg_cloud_auth; + +#endif /* SRC_INCLUDE_UTILS_CLOUDREST_H_ */ diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h index 38b217a..d1698a7 100644 --- a/src/include/utils/guc.h +++ b/src/include/utils/guc.h @@ -465,6 +465,12 @@ extern int rps_addr_port; /* interval of checking local RPS */ extern int rps_check_local_interval; + +/* + * cloud authenticate + */ +extern char *pg_cloud_clustername; + /* * During insertion in a table with parquet partitions, * require tuples to be sorted by partition key.
