I'm trying to find out if a specific MySQL User is still in use in our system (and what queries it is executing).
So I thought of writing a trigger that would kick in anytime user X executes a query, and it would log the query in a log table.
How can I do that?
I know how to write a query for a specific table, but not for a specific user (any table).
Thanks
You could branch your trigger function on USER().
The easiest would be to have the trigger always fire, but only logs if the user is X.
I would look at these options:
A) Write an audit plugin, which filters events based on the user name.
For simplicity, the user name can be hard coded in the plugin itself,
or for elegance, it can be configured by a plugin variable, in case this problem happens again.
See
http://dev.mysql.com/doc/refman/5.5/en/writing-audit-plugins.html
B) Investigate the --init-connect server option.
For example, call a stored procedure, check the value of user() / current_user(),
and write a trace to a log (insert into a table) if a connection from the user was seen.
See
http://dev.mysql.com/doc/refman/5.5/en/server-system-variables.html#sysvar_init_connect
This is probably the closest thing to a connect trigger.
C) Use the performance schema instrumentation.
This assumes 5.6.
Use table performance_schema.setup_instrument to only enable the statement instrumentation.
Use table performance_schema.setup_actors to only instrument sessions for this user.
Then, after the system has been running for a while, look at activity for this user in the following tables:
table performance_schema.users will tell if there was some activity at all
table performance_schema.events_statements_history_long will show the last queries executed
table performance_schema.events_statements_summary_by_user will show aggregate statistics about each statement types (SELECT, INSERT, ...) executed by this user.
Assuming you have a user defined as 'old_app'#'%', a likely follow up question will be to find out where (which host(s)) this old application is still connecting from.
performance_schema.accounts will just show that: if traffic for this user is seen, it will show each username # hostname source of traffic.
There are statistics aggregated by account also, look for '%_by_account%' tables.
See
http://dev.mysql.com/doc/refman/5.6/en/performance-schema.html
There are also other ways you could approach this problem, for example using MySQL proxy
In the proxy you could do interesting things - from logging to transforming queries, pattern matching (check this link also for details on how to test/develop the scripts)
-- set the username
local log_user = 'username'
function read_query( packet )
if proxy.connection.client.username == log_user and string.byte(packet) == proxy.COM_QUERY then
local log_file = '/var/log/mysql-proxy/mysql-' .. log_user .. '.log'
local fh = io.open(log_file, "a+")
local query = string.sub(packet, 2)
fh:write( string.format("%s %6d -- %s \n",
os.date('%Y-%m-%d %H:%M:%S'),
proxy.connection.server["thread_id"],
query))
fh:flush()
end
end
The above has been tested and it does what it is supposed to (although this is a simple variant, does not log success or failure and only logs proxy.COM_QUERY, see the list of all constants to see what is skipped and adjust for your needs)
Yeah, fire away, but use whatever system you have to see what user it is (cookies, session) to log only if the specific user (userID, class) matches your credentials.
Related
I'm trying to automate user creation within AWS. However, if I just write the user creation scripts, they will fail if re-run and users already exist.
I'm working in AWS Redshift.
I'd love to be able to do something like
CREATE USER IF NOT EXISTS usr_name
password '<random_secure_password>'
NOCREATEDB
NOCREATEUSER
;
however that doesn't seem possible.
Then I found CASE statements but it doesn't seem like CASE statements can work for me either.
i.e.
CASE WHEN
SELECT count(*) FROM pg_user WHERE usename = 'usr_name' = 0
THEN
CREATE USER usr_name
password '<random_secure_password>'
NOCREATEDB
NOCREATEUSER
END
Would this work? (Not a superuser so I can't test it myself)
If not, any ideas? Anything helps, thanks in advance.
If you're using psql you can use the \gexec metacommand:
\t on
BEGIN;
SELECT CASE WHEN (SELECT count(*) FROM pg_user WHERE usename = 'nonesuch') = 0
THEN 'CREATE USER nonesuch PASSWORD DISABLE'
END
\gexec
SELECT CASE WHEN (SELECT count(*) FROM pg_user WHERE usename = 'nonesuch') = 0
THEN 'CREATE USER nonesuch PASSWORD DISABLE'
ELSE 'SELECT \'user already exists, doing nothing\''
END
\gexec
ROLLBACK;
result:
BEGIN
CREATE USER
user already exists, doing nothing
ROLLBACK
https://www.postgresql.org/docs/9.6/app-psql.html (note you can't use format() as in the example since it's not implemented in Redshift)
As far as I know there is no mechanism to do this IN Redshift (at the moment) and making this transition to a more cloud model trips up many companies I've worked with. Many on-prem databases are expected to be their own operating world where all things must be done inside the database via SQL, whether or not these are data operations or not. What I call the "universal database" model. Redshift is part of the larger ecosystem of AWS cloud solutions where many of these administrative/management/flexibility operations are best done at the cloud layer, not the in database. The "database for data" model.
You didn't explained why you need to test for the existence of user and create them if they are not there or why this decision is being done in SQL. I do expect that some amount of 'this is how we have done it in the past' is in play. What you are looking to do can be a Lambda (with or without) Step Function, likely can be done in your ETL solution, or even written as a bash script. What you are looking to do is actually easy and I'd advise you think about doing it as part of a solution level architecture - not as a point database operation.
Now you may rightly ask 'if this is easy, why can't Redshift do it?' Fair point and one I've been asked many times. One answer is that Redshift is a cloud-based, large-data, analytic warehouse and as such it is designed to operate in that context. Another answer is that if enough large clients show the need and demand the functionality it will be added (AWS does react to meet general needs). Remember there are thousands of places new SQL command options can be added and all of them won't be added to Redshift - in fact enhancement requests of this type are made to almost every database frequently.
My advice is to take a step back and look at how your user and user-rights management should work for your solution and not just for the database. Then move to an architecture that manages these rights at the appropriate layer of the solution (whatever you decide that to be). Redshift users can be integrated with IAM which can be used to control access to other systems and other database. I know that this kind change takes work and time to complete (and can impact organizational roles) so until then I'd look at your existing database control systems (ETL, vacuum/analyze launcher, metrics collector etc.) and see which can meet your near-term needs.
Create a stored procedure with user name as param and have the check within. You can call this stored procedure in your deployment step. Only database creation cannot be done within a block. User can be created within a stored procedure.
CREATE OR REPLACE PROCEDURE sp_create_user(i_username varchar)
AS $$
DECLARE
t_user_count BIGINT;
BEGIN
SELECT count(1)
INTO t_user_count
FROM pg_user WHERE LOWER(usename) = 'username';
IF t_user_count>0 THEN
RAISE INFO 'User already exists';
ELSE
CREATE USER username WITH PASSWORD 'password' NOCREATEDB NOCREATEUSER;
END IF;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '[Error while creating user: username] Exception: %', SQLERRM;
END;
$$
LANGUAGE plpgsql;
I know maybe I'm asking something stupid in my application users can create a sort of agendas but only a specific number of agendas is allowed per day. So, users perform this pseudo-code:
select count(*) as created
from Agendas
where agendaDay = 'dd/mm/yyyy'
if created < allowedAgendas {
insert into Agendas ...
}
All this obviously MUST be executed in mutual exclusion. Only one user at time can read the number of created agendas and, possibly, insert a new one if allowed.
How can I do this?
I tried to open a transaction with default Read Committed isolation level but this doesn't help because during the transaction the other users can still get the number of the created agendas at the same time with a select query and so try
to insert a new one even if it wouldn't be allowed.
I don't think changing the isolation level could help.
How can I do this?
For testing I'm using SQL Server 2008 while in our production server SQL Server 2012 is run.
it sounds like you have an architecture problem there, but you may be able to achieve this requirement with:
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
If you're reading an inserting within the same transaction, I don't see where the problem will be, but if you're expecting interactive input on the basis of the count then you should probably ensure you do this within a single session of implement some kind of queuing functionality
My goal is to make trigger behavior to depend on some client identifier.
For example I execute a query
begin;
<specify-some-client-identifier>
insert into some_table
values('value')
commit;
And I have trigger function executing before insert:
NEW.some_filed := some_func(<some-client-identifier-spicified-above>)
So, how do I <specify-some-client-identifier> and get <some-client-identifier-spicified-above>?
You basically need some kind of variables in SQL. It is possible to do it, with multiple ways:
using GUCs
using table with variables
using temp table with variables
using %_SHARED in plperl functions
All this is possible. If you're interested in implementation details and/or comparison - check this blogpost - just in case it wasn't obvious from domain - it's my blog.
You will find this prior answer informative. There I explain how to pass an application-defined username through so it is visible to PostgreSQL functions and triggers.
You can also use the application_name GUC, which can be set by most client drivers or explicitly by the application. Depending on your purposes this may be sufficient.
Finally, you can examine pg_stat_activity to get info about the current client by looking it up by pg_backend_pid(). This will give you a client IP and port if TCP/IP is being used.
Of course, there's also current_user if you log in as particular users at the database level.
As usual, #depesz points out useful options I hadn't thought of, too - using shared context within PL/Perl, in particular. You can do the same thing in PL/Python. In both cases you'll pay the startup overhead of a full procedural language interpreter and the function call costs of accessing it, so it probably only makes sense to do this if you're already using PL/Perl or PL/Python.
I have a stored procedure that causes blocking on my SQL server database. Whenever it does block for more than X amount of seconds we get notified with what query is being run, and it looks similar to below.
CREATE PROC [dbo].[sp_problemprocedure] (
#orderid INT
--procedure code
How can I tell what the value is for #orderid? I'd like to know the value because this procedure will run 100+ times a day but only cause blocking a handful of times, and if we can find some sort of pattern between the order id's maybe I'd be able to track down the problem.
The procedure is being called from a .NET application if that helps.
Have you tried printing it from inside the procedure?
http://msdn.microsoft.com/en-us/library/ms176047.aspx
If it's being called from a .NET application you could easily log out the parameter being passed from the .net app, but if you don't have access, also you can use SQL Server profiling. Filters can be set on the command type i.e. proc only as well as the database that is being hit otherwise you will be overwhelmed with all the information a profile can produce.
Link: Using Sql server profiler
rename the procedure
create a logging table
create a new one (same signature/params) which calls the original but first logs the params and starting timestamp and logs after the call finishes the end timestamp
create a synonym for this new proc with the name of the original
Now you have a log for all calls made by whatever app...
You can disbale/enable the logging anytime by simply redefining the synonym to point to the logging wrapper or to the original...
The easiest way would be to run a profiler trace. You'll want to capture calls to the stored procedure.
Really though, that is only going to tell you part of the story. Personally I would start with the code. Try and batch big updates into smaller batches. Try and avoid long-running explicit transactions if they're not necessary. Look at your triggers (if any) and cascading Foreign keys and make sure those are efficient.
easiest way is to do the following:
1) in .NET, grab the date-time just before running the procedure
2) in .Net, after the procedure is complete grab the date-time
3) in .NET, do some date-time math, and if it is "slow", write to a file (log) those start and end date-times, user info, all the the parameters, etc.
I have a TSQL script that is used to set up a database as part of my product's installation. It takes a number of steps which all together take five minutes or so. Sometimes this script fails on the last step because the user running the script does not have sufficient rights to the database. In this case I would like the script to fail strait away. To do this I want the script to test what rights it has up front. Can anyone point me at a general purpose way of testing if the script is running with a particular security permission?
Edit: In the particular case I am looking at it is trying to do a backup, but I have had other things go wrong and was hoping for a general purpose solution.
select * from fn_my_permissions(NULL, 'SERVER')
This gives you a list of permissions the current session has on the server
select * from fn_my_permissions(NULL, 'DATABASE')
This gives you a list of permissions for the current session on the current database.
See here for more information.
I assume it is failing on an update or insert after a long series of selects.
Just try a simple update or insert inside a transaction. Hard-code the row id, or whatever to make it simple and fast.
Don't commit the transaction--instead roll it back.
If you don't have rights to do the insert or update, this should fail. If you DO, it will roll back and not cause a permanent change.
try the last insert/update up front with some where condition like
insert/update
where 1=2
if (##error <> 0)
raise error 6666 'no permissions'
this would not cause any harm but would raise a flag upfront about the lack of rights.