Granting SET and ALTER SYSTE privileges for GUCs
Hackers,
In the ongoing effort [1] to reduce the number of tasks which require operating under superuser privileges, this patch extends the system to allow, per GUC variable, the ability to SET or ALTER SYSTEM for the variable. A previous patch set was submitted [2] which created hard-coded privileged roles with the authority to manage associated hard-coded sets of GUC variables. This current patch appears superior in several ways:
- It allows much greater flexibility in how roles and GUCs are associated
- Custom GUC variables defined by extensions can be covered by this approach
and perhaps most importantly,
- It's what Andrew suggested
Granting SET privilege on a USERSET variable makes no practical difference, but for SUSET variables it does, and granting ALTER SYSTEM is meaningful for all variables. The patch does not mandate that non-login roles be created for this, but as a usage suggestion, one could define a non-login role and assign privileges for a set of GUCs, such as:
CREATE ROLE regress_host_resource_admin NOSUPERUSER;
GRANT SET VALUE, ALTER SYSTEM ON
autovacuum_work_mem, hash_mem_multiplier, logical_decoding_work_mem,
maintenance_work_mem, max_stack_depth, min_dynamic_shared_memory,
shared_buffers, temp_buffers, temp_file_limit, work_mem
TO regress_host_resource_admin;
and then delegate authority to manage the set of GUCs to a non-superuser by granting membership in non-login role:
CREATE ROLE regress_admin_member IN ROLE regress_host_resource_admin;
One disadvantage of this approach is that the GUC variables are represented both in the list of C structures in guc.c and in the new system catalog pg_config_param's .dat file. Failure to enter a GUC in the .dat file will result in the inability to grant privileges on the GUC, at least unless/until you run CREATE CONFIGURATION PARAMETER on the GUC. (This is, in fact, how extension scripts deal with the issue.) It would perhaps be better if the list of GUCs were not duplicated, but I wasn't clever enough to find a clean way to do that without greatly expanding the patch (nor did I complete prototyping any such thing.)
Attachments:
v2-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-for-variables.patchapplication/octet-stream; name=v2-0001-Allow-GRANT-of-SET-and-ALTER-SYSTEM-for-variables.patch; x-unix-mode=0644Download+1847-38
On Mon, Nov 15, 2021 at 3:37 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
One disadvantage of this approach is that the GUC variables are represented both in the list of C structures in guc.c and in the new system catalog pg_config_param's .dat file. Failure to enter a GUC in the .dat file will result in the inability to grant privileges on the GUC, at least unless/until you run CREATE CONFIGURATION PARAMETER on the GUC. (This is, in fact, how extension scripts deal with the issue.) It would perhaps be better if the list of GUCs were not duplicated, but I wasn't clever enough to find a clean way to do that without greatly expanding the patch (nor did I complete prototyping any such thing.)
I think this imposes an unnecessary burden on developers. It seems
like it would be easy to write some code that lives inside guc.c and
runs during bootstrap, and it could just iterate over each
ConfigureNamesWhatever array and insert catalog entries for everything
it finds.
It's also going to be important to think about what happens with
extension GUCs. If somebody installs an extension, we can't ask them
to perform a manual step in order to be able to grant privileges. And
if somebody then loads up a different .so for that extension, the set
of GUCs that it provides can change without any DDL being executed.
New GUCs could appear, and old GUCs could vanish.
So I wonder if we should instead not do what I proposed above, and
instead just adjust the GRANT command to automatically insert a new
row into the relevant catalog if there isn't one already. That seems
nicer for extensions, and also nicer for core GUCs, since it avoids
bloating the catalog with a bunch of entries that aren't needed.
--
Robert Haas
EDB: http://www.enterprisedb.com
Robert Haas <robertmhaas@gmail.com> writes:
It's also going to be important to think about what happens with
extension GUCs. If somebody installs an extension, we can't ask them
to perform a manual step in order to be able to grant privileges. And
if somebody then loads up a different .so for that extension, the set
of GUCs that it provides can change without any DDL being executed.
New GUCs could appear, and old GUCs could vanish.
Right. I think that any design that involves per-GUC catalog entries
is going to be an abysmal failure, precisely because the set of GUCs
is not stable enough. So I'm suspicious of this entire proposal.
Maybe there's a way to make it work, but that way isn't how.
regards, tom lane
On Tue, Nov 16, 2021 at 10:17 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Right. I think that any design that involves per-GUC catalog entries
is going to be an abysmal failure, precisely because the set of GUCs
is not stable enough.
In practice it's pretty stable. I think it's just a matter of having a
plan that covers the cases where it isn't stable reasonably elegantly.
We already embed GUC names in catalog entries when someone runs ALTER
USER SET or ALTER DATABASE SET, so this proposal doesn't seem to be
moving the goalposts in any meaningful way.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Nov 16, 2021, at 7:03 AM, Robert Haas <robertmhaas@gmail.com> wrote:
It's also going to be important to think about what happens with
extension GUCs. If somebody installs an extension, we can't ask them
to perform a manual step in order to be able to grant privileges.
The burden isn't on the installer of an extension. As implemented, it's the extension's installation .sql file that sets it up, and the upgrade .sql files that make adjustments, if necessary.
And
if somebody then loads up a different .so for that extension, the set
of GUCs that it provides can change without any DDL being executed.
New GUCs could appear, and old GUCs could vanish.
Well, the same is true for functions, right? If you add, remove, or redefine functions in the extension, you need an upgrade script that defines the new functions, removes the old ones, changes function signatures, or whatever. The same is true here for GUCs.
I don't think we support using a .so that is mismatched against the version of the extension that is installed.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Robert Haas <robertmhaas@gmail.com> writes:
On Tue, Nov 16, 2021 at 10:17 AM Tom Lane <tgl@sss.pgh.pa.us> wrote:
Right. I think that any design that involves per-GUC catalog entries
is going to be an abysmal failure, precisely because the set of GUCs
is not stable enough.
In practice it's pretty stable. I think it's just a matter of having a
plan that covers the cases where it isn't stable reasonably elegantly.
Um. Really the point comes down to having sane default behavior
when there's no entry, which ought to eliminate any need to do the
sort of "run over all the entries at startup" processing that you
seemed to be proposing. So I guess I don't understand why such a
thing would be needed.
We already embed GUC names in catalog entries when someone runs ALTER
USER SET or ALTER DATABASE SET, so this proposal doesn't seem to be
moving the goalposts in any meaningful way.
True; as long as the expectation is that entries will exist for only
a tiny subset of GUCs, it's probably fine.
regards, tom lane
Mark Dilger <mark.dilger@enterprisedb.com> writes:
I don't think we support using a .so that is mismatched against the
version of the extension that is installed.
You are entirely mistaken. That's not only "supported", it's *required*.
Consider binary upgrades, where the SQL objects are transferred as-is
but the receiving installation may have a different (hopefully newer)
version of the .so. That .so has to cope with the older SQL object
definitions; it doesn't get to assume that the upgrade script has been
run yet.
regards, tom lane
On Nov 16, 2021, at 7:32 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
Mark Dilger <mark.dilger@enterprisedb.com> writes:
I don't think we support using a .so that is mismatched against the
version of the extension that is installed.You are entirely mistaken. That's not only "supported", it's *required*.
Consider binary upgrades, where the SQL objects are transferred as-is
but the receiving installation may have a different (hopefully newer)
version of the .so. That .so has to cope with the older SQL object
definitions; it doesn't get to assume that the upgrade script has been
run yet.
You are talking about mismatches in the other direction, aren't you? I was responding to Robert's point that new gucs could appear, and old gucs disappear. That seems analogous to new functions appearing and old functions disappearing. If you upgrade (not downgrade) the .so, the new gucs and functions will be in the .so, but won't be callable/grantable from sql until the upgrade script runs. The old gucs and functions will be missing from the .so, and attempts to call them/grant them from sql before the upgrade will fail. What am I missing here?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Nov 16, 2021, at 7:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
True; as long as the expectation is that entries will exist for only
a tiny subset of GUCs, it's probably fine.
I understand that bloating a frequently used catalog can be pretty harmful to performance. I wasn't aware that the size of an infrequently used catalog was critical. This new catalog would be used during GRANT SET ... and GRANT ALTER SYSTEM commands, which should be rare, and potentially consulted when SET or ALTER SYSTEM commands are issued. Is there a more substantial performance impact to this than I'm aware? It can be a bit challenging to run performance tests on such things, given the way everything interacts with everything else.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Nov 16, 2021 at 10:45 AM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
You are talking about mismatches in the other direction, aren't you? I was responding to Robert's point that new gucs could appear, and old gucs disappear. That seems analogous to new functions appearing and old functions disappearing. If you upgrade (not downgrade) the .so, the new gucs and functions will be in the .so, but won't be callable/grantable from sql until the upgrade script runs. The old gucs and functions will be missing from the .so, and attempts to call them/grant them from sql before the upgrade will fail. What am I missing here?
It's true that we could impose such a restriction, but I don't think
we should. If I install a different .so, I want the new GUCs to be
grantable immediately, without running any separate DDL.
I also don't think we should burden extension authors with putting
stuff in their upgrade scripts for this. We should solve the problem
in our code rather than forcing them to do so in theirs.
--
Robert Haas
EDB: http://www.enterprisedb.com
Mark Dilger <mark.dilger@enterprisedb.com> writes:
You are talking about mismatches in the other direction, aren't you? I was responding to Robert's point that new gucs could appear, and old gucs disappear. That seems analogous to new functions appearing and old functions disappearing. If you upgrade (not downgrade) the .so, the new gucs and functions will be in the .so, but won't be callable/grantable from sql until the upgrade script runs.
True, but in general users might not care about getting access to new
features (or at least, they might not care until much later, at which
point they'd bother to run ALTER EXTENSION UPDATE).
The old gucs and functions will be missing from the .so, and attempts to call them/grant them from sql before the upgrade will fail. What am I missing here?
Here, you are describing behavior that's against project policy and would
be rejected immediately if anyone submitted a patch that made an extension
do that. Newer .so versions are expected to run successfully, not fail,
with an older version of their SQL objects. Were this not so, it's almost
inevitable that a binary upgrade would fail, because the extension is
likely to be called into action somewhere before there is any opportunity
to issue ALTER EXTENSION UPDATE. Even in-place updates of extensions
would become problematic: you can't assume that a rollout of new
executables will be instantly followed by ALTER EXTENSION UPDATE.
Basically, I think the proposed new system catalog is both unworkable
and entirely unnecessary. Maybe it would have been okay if Peter had
designed GUCs like that a couple of decades ago, but at this point
we have a ton of infrastructure --- much of it not under the core
project's control --- that assumes that GUCs can be created with just
a bit of code. I do not think that this feature is worth the cost of
changing that. Or IOW: ALTER SYSTEM SET has gotten along fine without
such a catalog; why can't this feature?
I also notice that the patch seems to intend to introduce tracking
of which user "owns" a GUC, which I think is even more unworkable.
They are all going to wind up owned by the bootstrap superuser
(extension GUCs too), so why bother?
regards, tom lane
Mark Dilger <mark.dilger@enterprisedb.com> writes:
On Nov 16, 2021, at 7:28 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
True; as long as the expectation is that entries will exist for only
a tiny subset of GUCs, it's probably fine.
I understand that bloating a frequently used catalog can be pretty
harmful to performance. I wasn't aware that the size of an infrequently
used catalog was critical.
My concern is not about performance, it's about the difficulty of
maintaining a catalog that expects to be a more-or-less exhaustive
list of GUCs. I think you need to get rid of that expectation.
In the analogy to ALTER DATABASE/USER SET, we don't expect that
pg_db_role_setting catalog entries will exist for all, or even
very many, GUCs. Also, the fact that pg_db_role_setting entries
aren't tied very hard to the actual existence of a GUC is a good
thing from the maintenance and upgrade standpoint.
BTW, if we did create such a catalog, there would need to be
pg_dump and pg_upgrade support for its entries, and you'd have to
think about (e.g.) whether pg_upgrade would attempt to preserve
the same OIDs. I don't see any indication that the patch has
addressed that infrastructure ... which is probably just as well,
since it's work that I'd be wanting to reject. (Hm, but actually,
doesn't pg_dump need work anyway to dump this new type of GRANT?)
regards, tom lane
On Nov 16, 2021, at 8:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
My concern is not about performance, it's about the difficulty of
maintaining a catalog that expects to be a more-or-less exhaustive
list of GUCs. I think you need to get rid of that expectation.
I'm preparing a new version of the patch that has the catalog empty to begin with, and only adds values in response to GRANT commands. That also handles the issues of extension upgrades, which I think the old patch handled better than the discussion on this thread suggested, but no matter. The new behavior will allow granting privileges on non-existent gucs, just as ALTER ROLE..SET allows registering settings on non-existent gucs.
The reason I had resisted allowing grants of privileges on non-existent gucs is that you can get the following sort of behavior (note the last two lines):
DROP USER regress_priv_user7;
ERROR: role "regress_priv_user7" cannot be dropped because some objects depend on it
DETAIL: privileges for table persons2
privileges for configuration parameter sort_mem
privileges for configuration parameter no_such_param
Rejecting "no_such_param" in the original GRANT statement seemed cleaner to me, but this discussion suggests pretty strongly that I can't do it that way. Changing the historical "sort_mem" to the canonical "work_mem" name also seems better to me, as otherwise you could have different grants on the same GUC under different names. I'm inclined to keep the canonicalization of names where known, but maybe that runs afoul the rule that these grants should not be tied very hard to the GUC?
In the analogy to ALTER DATABASE/USER SET, we don't expect that
pg_db_role_setting catalog entries will exist for all, or even
very many, GUCs. Also, the fact that pg_db_role_setting entries
aren't tied very hard to the actual existence of a GUC is a good
thing from the maintenance and upgrade standpoint.
Doing it that way....
BTW, if we did create such a catalog, there would need to be
pg_dump and pg_upgrade support for its entries, and you'd have to
think about (e.g.) whether pg_upgrade would attempt to preserve
the same OIDs. I don't see any indication that the patch has
addressed that infrastructure ... which is probably just as well,
since it's work that I'd be wanting to reject.
Yeah, that's why I didn't write it. I wanted feedback on the basic implementation before doing that work.
(Hm, but actually,
doesn't pg_dump need work anyway to dump this new type of GRANT?)
Yes, if the idea of this kind of grant isn't being outright rejected, then I'll need to write that.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On 11/16/21 14:48, Mark Dilger wrote:
On Nov 16, 2021, at 8:44 AM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
My concern is not about performance, it's about the difficulty of
maintaining a catalog that expects to be a more-or-less exhaustive
list of GUCs. I think you need to get rid of that expectation.I'm preparing a new version of the patch that has the catalog empty to begin with, and only adds values in response to GRANT commands. That also handles the issues of extension upgrades, which I think the old patch handled better than the discussion on this thread suggested, but no matter. The new behavior will allow granting privileges on non-existent gucs, just as ALTER ROLE..SET allows registering settings on non-existent gucs.
Your original and fairly simple set of patches used hardcoded role names
and sets of GUCs they could update via ALTER SYSTEM. I suggested to you
privately that a more flexible approach would be to drive this from a
catalog table. I had in mind a table of more or less <roleid, guc_name>.
You could prepopulate it with the roles / GUCs from your original patch
set. I don't think it needs to be initially empty. But DBAs would be
able to modify and extend the settings. I agree with Tom that we
shouldn't try to cover all GUCs in the table - any GUC without an entry
can only be updated by a superuser.
To support pg_dump and pg_upgrade, it might be better to have an
enabled/disabled flag rather than to delete rows.
cheers
andrew
--
Andrew Dunstan
EDB: https://www.enterprisedb.com
On Nov 16, 2021, at 12:38 PM, Andrew Dunstan <andrew@dunslane.net> wrote:
Your original and fairly simple set of patches used hardcoded role names
and sets of GUCs they could update via ALTER SYSTEM. I suggested to you
privately that a more flexible approach would be to drive this from a
catalog table. I had in mind a table of more or less <roleid, guc_name>.
Right. I prefer a table of <guc_name, acl> matching the structure of most of the rest of the grantable objects, so that entries from pg_depend or pg_shdepend can track the dependencies in the usual way and prevent dangling references when DROP ROLE is executed. Is there a reason to prefer an ad hoc tables structure?
You could prepopulate it with the roles / GUCs from your original patch
set. I don't think it needs to be initially empty. But DBAs would be
able to modify and extend the settings. I agree with Tom that we
shouldn't try to cover all GUCs in the table - any GUC without an entry
can only be updated by a superuser.
The patch already posted on this thread already works that way. Robert and Tom seemed to infer that all gucs need to be present in the catalog, but in fact, not entering them in the catalog simply means that they aren't grantable. I think the confusion arose from the fact that I created a mechanism for extension authors to enter additional gucs into the catalog, which gave the false impression that such entries were required. They are not. I didn't bother explaining that before, because Robert and Tom both seem to expect all gucs to be grantable, an expectation you do not appear to share.
To support pg_dump and pg_upgrade, it might be better to have an
enabled/disabled flag rather than to delete rows.
I'm not really sure what this means. Are you suggesting that the <guc_name, acl> (or in your formulation <roleid, guc_name>) should have a "bool enabled" field, and when the guc gets dropped, the "enabled" field gets set to false?
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
Mark Dilger <mark.dilger@enterprisedb.com> writes:
The patch already posted on this thread already works that way. Robert and Tom seemed to infer that all gucs need to be present in the catalog, but in fact, not entering them in the catalog simply means that they aren't grantable. I think the confusion arose from the fact that I created a mechanism for extension authors to enter additional gucs into the catalog, which gave the false impression that such entries were required. They are not. I didn't bother explaining that before, because Robert and Tom both seem to expect all gucs to be grantable, an expectation you do not appear to share.
The question is why you need pg_config_param at all, then.
AFAICS it just adds maintenance complexity we could do without.
I think we'd be better off with a catalog modeled on the design of
pg_db_role_setting, which would have entries for roles and lists
of GUC names that those roles could set.
BTW, another objection to pg_config_param as designed here is that
a "name" is not an appropriate way to store possibly-qualified
custom GUC names. It's not long enough (cf. valid_custom_variable_name).
To support pg_dump and pg_upgrade, it might be better to have an
enabled/disabled flag rather than to delete rows.
I'm not really sure what this means.
I didn't see the point of this either. We really need to KISS here.
Every bit of added complexity in the catalog representation is another
opportunity for bugs-of-omission, not to mention a detail that you
have to provide mechanisms to dump and restore.
regards, tom lane
On Nov 16, 2021, at 2:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
The question is why you need pg_config_param at all, then.
AFAICS it just adds maintenance complexity we could do without.
I think we'd be better off with a catalog modeled on the design of
pg_db_role_setting, which would have entries for roles and lists
of GUC names that those roles could set.
Originally, I was trying to have dependency linkage between two proper types of objects, so that DROP ROLE and DROP CONFIGURATION PARAMETER would behave as expected, complaining about privileges remaining rather than dropping an object and leaving a dangling reference.
I've deleted the whole CREATE CONFIGURATION PARAMETER and DROP CONFIGURATION PARAMETER syntax and implementation, but it still seems odd to me that:
CREATE ROLE somebody;
GRANT SELECT ON TABLE sometable TO ROLE somebody;
GRANT ALTER SYSTEM ON someguc TO ROLE somebody;
DROP ROLE somebody;
ERROR: role "somebody" cannot be dropped because some objects depend on it
DETAIL: privileges for table sometable
would not mention privileges for "someguc" as well. That's why I want configuration parameters to be proper objects with OIDs and with entries in pg_depend and/or pg_shdepend. Maybe there is some better way to do it, but that's why I've been doing this.
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Nov 16, 2021, at 2:12 PM, Tom Lane <tgl@sss.pgh.pa.us> wrote:
BTW, another objection to pg_config_param as designed here is that
a "name" is not an appropriate way to store possibly-qualified
custom GUC names. It's not long enough (cf. valid_custom_variable_name).
I was aware of that, but figured not all GUCs have to be grantable. If it doesn't fit in a NameData, you can't grant on it.
If we want to be more accommodating than that, we can store it as text, just like pg_db_role_names does, but then we need more code complexity to look it up and to verify that it is unique. (We wouldn't want multiple records for the same <role,guc> pair.)
—
Mark Dilger
EnterpriseDB: http://www.enterprisedb.com
The Enterprise PostgreSQL Company
On Tue, Nov 16, 2021 at 2:48 PM Mark Dilger
<mark.dilger@enterprisedb.com> wrote:
I'm preparing a new version of the patch that has the catalog empty to begin with, and only adds values in response to GRANT commands. That also handles the issues of extension upgrades, which I think the old patch handled better than the discussion on this thread suggested, but no matter. The new behavior will allow granting privileges on non-existent gucs, just as ALTER ROLE..SET allows registering settings on non-existent gucs.
The reason I had resisted allowing grants of privileges on non-existent gucs is that you can get the following sort of behavior (note the last two lines):
DROP USER regress_priv_user7;
ERROR: role "regress_priv_user7" cannot be dropped because some objects depend on it
DETAIL: privileges for table persons2
privileges for configuration parameter sort_mem
privileges for configuration parameter no_such_paramRejecting "no_such_param" in the original GRANT statement seemed cleaner to me, but this discussion suggests pretty strongly that I can't do it that way.
I think it's perfectly fine to refuse a GRANT statement on a GUC that
doesn't exist, and I don't believe I ever said otherwise. What I don't
think is OK is to require a preparatory statement like CREATE
CONFIGURATION PARAMETER to be executed before you can grant
permissions on it. There's no reason for that. If somebody tries to
grant privileges on a GUC that does not exist, fail. If the GUC does
exist but there's no catalog entry, make one. If the GUC exists and
the catalog entry also exists, update it.
At REVOKE time, don't check whether the GUC exists - only check the
catalog. That way people can remove privileges on GUCs that don't
exist any more. If somebody issues a REVOKE and there's no catalog
entry, do nothing. If somebody issues a REVOKE and there is a catalog
entry, remove stuff from it; but if that would leave it completely
empty, instead delete it.
Whenever you create a catalog entry, also add dependency entries
pointing to it. When you delete one, remove those entries.
Changing the historical "sort_mem" to the canonical "work_mem" name also seems better to me, as otherwise you could have different grants on the same GUC under different names. I'm inclined to keep the canonicalization of names where known, but maybe that runs afoul the rule that these grants should not be tied very hard to the GUC?
No. If somebody grants privileges on an old name, record the grant
under the canonical name.
--
Robert Haas
EDB: http://www.enterprisedb.com
On Tue, Nov 16, 2021 at 3:38 PM Andrew Dunstan <andrew@dunslane.net> wrote:
Your original and fairly simple set of patches used hardcoded role names
and sets of GUCs they could update via ALTER SYSTEM. I suggested to you
privately that a more flexible approach would be to drive this from a
catalog table. I had in mind a table of more or less <roleid, guc_name>.
You could prepopulate it with the roles / GUCs from your original patch
set. I don't think it needs to be initially empty. But DBAs would be
able to modify and extend the settings. I agree with Tom that we
shouldn't try to cover all GUCs in the table - any GUC without an entry
can only be updated by a superuser.
I simply can't understand the point of this. You're basically
proposing that somebody has to execute one SQL statement to make a GUC
grantable, and then a second SQL statement to actually grant access to
it. What is the value in that? It is the same person doing both
things, and the system can work out automatically what needs to be
done.
--
Robert Haas
EDB: http://www.enterprisedb.com