SQL Server - linked server disappears - sql-server

For 6 months I had linked servers working properly in SQL Server. Everything worked fine, but something strange started to happen. A week ago I was notified that synchronization was not working. I discovered that the linked servers have disappeared. I added them again but after 3 days again but after 3 days they disappeared again.
I didn't delete them manually.
Is it possible that something removed them?
How to protect against automatic removal?
Windows Server 2019, SQL Server 2017

Create a table that will hold tracked operations, something like:
CREATE TABLE EventLogTable (
EventLogTableID INT IDENTITY PRIMARY KEY,
EventType NVARCHAR(100),
PostTime DATETIME,
SPID INT,
ServerName NVARCHAR(100),
LoginName NVARCHAR(100),
ObjectName NVARCHAR(100),
ObjectType NVARCHAR(100),
[Parameters] NVARCHAR(1000),
TargetObjectName NVARCHAR(100),
TargetObjectType NVARCHAR(100),
TSQLCommand NVARCHAR(1000))
Then create a DDL server trigger that monitors linked server events, through the EVENTDATA() function:
CREATE TRIGGER utrLogLinkedServerOperations ON ALL SERVER FOR
CREATE_LINKED_SERVER,
DROP_LINKED_SERVER,
ALTER_LINKED_SERVER
AS
BEGIN
DECLARE #EventXML XML = EVENTDATA()
INSERT INTO EventLogTable (
EventType,
PostTime,
SPID,
ServerName,
LoginName,
ObjectName,
ObjectType,
[Parameters],
TargetObjectName,
TargetObjectType,
TSQLCommand)
SELECT
EventType = #EventXML.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)'),
PostTime = #EventXML.value('(/EVENT_INSTANCE/PostTime)[1]', 'nvarchar(100)'),
SPID = #EventXML.value('(/EVENT_INSTANCE/SPID)[1]', 'nvarchar(100)'),
ServerName = #EventXML.value('(/EVENT_INSTANCE/ServerName)[1]', 'nvarchar(100)'),
LoginName = #EventXML.value('(/EVENT_INSTANCE/LoginName)[1]', 'nvarchar(100)'),
ObjectName = #EventXML.value('(/EVENT_INSTANCE/ObjectName)[1]', 'nvarchar(100)'),
ObjectType = #EventXML.value('(/EVENT_INSTANCE/ObjectType)[1]', 'nvarchar(100)'),
[Parameters] = #EventXML.value('(/EVENT_INSTANCE/Parameters)[1]', 'NVARCHAR(1000)'),
TargetObjectName = #EventXML.value('(/EVENT_INSTANCE/TargetObjectName)[1]','nvarchar(100)'),
TargetObjectType = #EventXML.value('(/EVENT_INSTANCE/TargetObjectType)[1]', 'nvarchar(100)'),
TSQLCommand = #EventXML.value('(/EVENT_INSTANCE/TSQLCommand)[1]', 'NVARCHAR(1000)')
END
You can find the XML schema in here. Make sure to enable the trigger:
ENABLE TRIGGER utrLogLinkedServerOperations ON ALL SERVER
Now try creating, modifying and dropping a few linked servers to check that the trigger is correctly creating the log in the table. Then wait for the ninja to attack.
You could also rollback the operation inside the trigger but beware, you might end up not allowing even desired processes to manipulate linked servers.

Dropping linked server requires ALTER ANY LINKED SERVER permission.
To find out who could do it you should find who has this permission at the server level (my second query that searches in sys.server_permissions). It's rare that someone has this permission granted explicitly, so you should search among sysadmins and setupadmins too.
I also include principals that have control server permission to my search.
Note that even if you can set up DDL-trigger someone with control server or just sysadmin can easily disable it when he wants to drop linked servers and not be captured.
select sp.name as RoleName,
member.name as MemberName
from sys.server_role_members rm
join sys.server_principals as sp
on rm.role_principal_id = sp.principal_id
join sys.server_principals as member
on rm.member_principal_id = member.principal_id
where sp.name in ('sysadmin', 'setupadmin');
select suser_name(grantee_principal_id), permission_name
from sys.server_permissions
where permission_name in ('ALTER ANY LINKED SERVER', 'CONTROL SERVER');

Related

Determine database permissions for database users with valid server logins

I'm looking to find a script that will provide me with the details of what permissions our current SQL server logins have. I am able to script out the current permissions of our database users. The issue I have is that a lot of the database users no longer have a corresponding SQL server login due to a variety of reasons such as domain migrations & staff leaving the company.
The script used to get the database user permissions is : <credit to Nag Pal MCTS/MCITP (SQL Server 2005/2008) this was taken from the old MSDN forum>
DECLARE #DBuser_sql VARCHAR(4000)
DECLARE #DBuser_table TABLE (DBName VARCHAR(200), UserName VARCHAR(250), LoginType VARCHAR(500), AssociatedRole VARCHAR(200))
SET #DBuser_sql='SELECT ''?'' AS DBName,a.name AS Name,a.type_desc AS LoginType,USER_NAME(b.role_principal_id) AS AssociatedRole FROM ?.sys.database_principals a
LEFT OUTER JOIN ?.sys.database_role_members b ON a.principal_id=b.member_principal_id
WHERE a.sid NOT IN (0x01,0x00) AND a.sid IS NOT NULL AND a.type NOT IN (''C'') AND a.is_fixed_role <> 1 AND a.name NOT LIKE ''##%'' AND ''?'' NOT IN (''master'',''msdb'',''model'',''tempdb'') ORDER BY Name'
INSERT #DBuser_table
EXEC sp_MSforeachdb #command1=#dbuser_sql
SELECT * FROM #DBuser_table ORDER BY DBName
Thank you

how to trace SQL table drop create events

I have some mysterious problem where every day one table in DB (SQL Server 2016) is being recreated (I suppose dropped and created) with old data. I checked various options to try to find what process is doing this, however was unable to do that.
Scheduled Tasks - nothing
SQL Agent Jobs - nothing
How to trace what user/application/anythingelse is doing this ?
I tried launching SQL Profiler and starting manual trace, but after some time (half a day or so) it just stopped.
The default trace captures schema changes. Review the Schema Change History report or run the query below to retrieve the info in T-SQL. Note that the default trace rollover files are limited to 5 files of up to 20MB each so older events may have rolled off.
--select object created and deleted information from default trace
SELECT
trace_table.StartTime
, te.name
, trace_table.ObjectName
, trace_table.ApplicationName
, trace_table.LoginName
FROM (
SELECT REVERSE(SUBSTRING(REVERSE(path), CHARINDEX('\', REVERSE(path)) , 255)) + 'log.trc'
FROM sys.traces
WHERE
is_default = 1
) AS trace(path)
CROSS APPLY sys.fn_trace_gettable(trace.path, DEFAULT) AS trace_table
JOIN sys.trace_events AS te ON
te.trace_event_id = trace_table.EventClass
WHERE
EventSubClass = 0
AND name IN('Object:Created', 'Object:Deleted')
ORDER BY StartTime;
create a database trigger and log the create/drop table events:
create table dbo.traceTabledropcreate(EventDataXML xml, LogDatetime datetime default(getdate()));
go
create or alter trigger dbtrigger_traceTabledropcreate
on database
with execute as 'dbo'
for CREATE_TABLE, DROP_TABLE
as
begin
set nocount on;
--insert into dbo.traceTabledropcreate(EventDataXML)
--values (EVENTDATA());
declare #sessionxml xml =
(
select EVENTDATA(),
(
select *
from sys.dm_exec_sessions
where session_id = ##spid
for xml path('sessioninfo'), type
)
for xml path('')
);
insert into dbo.traceTabledropcreate(EventDataXML)
values (#sessionxml);
end
go
---..... and wait....
--..test
create table dbo.testtable(id int)
go
select *
from dbo.traceTabledropcreate
go
drop table dbo.testtable
go
select *
from dbo.traceTabledropcreate
go

Convert SQL Server trigger to Oracle and master.dbo.sysprocesses - not duplicate

I tried searching but could not find exactly what I'm looking for. I'm new to SQl Server and involved into SQL Server to Oracle conversion, and it is manual conversion. All I have is SQL Server files.
I see two types of SQL Server triggers - FOR UPDATE and FOR INSERT. They look to me as before update and before insert triggers in Oracle. I'd like to confirm this please and if you can provide examples that would be great.
Also, what is the equivalent to master.dbo.sysprocesses in Oracle please? Is this v$session? I can get user from dual in Oracle. Is this what nt_username is in below code?
Here is typical code examples I need to convert to Oracle - is this before insert?
CREATE TRIGGER trigger_name ON dbo.table_name
FOR Insert AS
declare #InsertUser varchar(32)
BEGIN
SELECT #InsertUser = nt_username from master.dbo.sysprocesses where spid = ##spid
Update table_name
SET dCreateDate = GETDATE(), cCreateUser = #InsertUser
FROM table1 a ,table2 i WHERE a.tab_id = i.tab_id
END
GO
Update Trigger - before update?
CREATE TRIGGER trigger_name ON dbo.table_name
FOR UPDATE AS
declare #UpdateUser varchar(32)
if not update(CreateUser) and not update(CreateDate)
BEGIN
SELECT #UpdateUser = nt_username from master.dbo.sysprocesses where spid = ##spid
Update table_name
SET UpdateDate = GETDATE(), UpdateUser = #UpdateUser
FROM table1 a ,table2 i WHERE a.tab_id = i.tab_id
END
GO
Should I combine these two into if inserting... elsif updating in Oracle?
Thank you very much to all.

How to restrict user access to tables, but not to SPs and Views in SQL Server 2008

Prerequisites:
Let's say I have 2 tables.
Table A
With columns A, B and C.
Table B with columns A, B and C.
I also have a stored procedure to update both tables
and I have a simple View that joins the tables.
I also have a user, let's call him... "Bob".
Question:
Now;
I want "Bob" to only have access to the view and the stored procedure.
"Bob" cannot gain read/write to either table, only to the view and the stored procedure.
How would I achieve this?
Old question, but ownership chaining in SQL is the answer. The comments to the question had the answer to the SP issue, but OP implied an issue with running select against the view.
For the SP, you need to specify the execute as owner clause
CREATE PROCEDURE dbo.YourProc
WITH EXECUTE AS OWNER
AS
--Your code here
GO
GRANT EXEC ON dbo.YourProc TO Bob;
GO
And if the owner of the SP has access to those tables, this will work.
For the view, same idea. I stumbled because I created a new view/schema to limit access, I gave the user select permission on the view, but the owner of the schema did NOT have access to the underlying tables, so when the user executed the select, I got an error about not having permissions to run select against the underlying table. Changing the owner of the schema to dbo (which owned the tables) fixed this for me.
I put together a script to stage this a bit, and gives the user select permissions on the schema instead of just the view (as I know this won't be the only view they'll want)
DECLARE #Schema varchar(128) = 'Reporting', #Username varchar(255) = 'ReportingUser', #dbName varchar(255)
SELECT #dbName=db_name(dbid) FROM master.dbo.sysprocesses WHERE spid=##spid
IF NOT EXISTS (SELECT * FROM master.sys.server_principals where name = #Username)
BEGIN
Print 'User must be created/added to SQL before being given database permissions'
GOTO TheEnd
END
IF NOT EXISTS (SELECT * FROM sysusers where name = #Username)
BEGIN
EXEC sp_grantdbaccess #Username,#Username
END
IF NOT EXISTS (SELECT * FROM sys.schemas WHERE name = #SCHEMA)
BEGIN
EXEC('CREATE SCHEMA ' + #Schema)
END
EXEC ('ALTER USER '+#Username+' WITH DEFAULT_SCHEMA = ' + #Schema)
EXEC ('GRANT SELECT ON SCHEMA :: '+ #Schema +' TO '+ #Username)
TheEnd:
And to save some folks to stumble over this too some time, here are some helpful queries to identify and troubleshoot ownership:
SELECT s.name AS SchemaName,
s.schema_id,
u.name AS schema_owner
FROM sys.schemas s
INNER JOIN sys.sysusers u ON u.uid = s.principal_id
ORDER BY s.name;
This will show you schema owners
exec sp_tables #table_type = "'table', 'view'"
This will show you owner of the views/tables

SQL Server track DDL CREATE USER

I am trying to track user creation. I have looked at the DDL triggers in many posts but those seem only to track objects, not users. Is there a way for me to track/record when a user is created or deleted in SQL Server?
CREATE_USER is absolutely a trackable DDL event, as is DROP_USER, and both have been since SQL Server 2005. BOL is hard-pressed for decent examples, though. The truth is the DDL trigger eventdata schema is not flexible enough to always have an entity named the way you want (like UserName). It's not intuitive, and may be the source of your confusion, but you actually need to pull the name of the created user from ObjectName:
USE [your_database_name];
GO
CREATE TRIGGER CatchUser
ON DATABASE
FOR CREATE_USER, DROP_USER
AS
BEGIN
SET NOCOUNT ON;
DECLARE #x XML = EVENTDATA();
-- INSERT dbo.LoggingTable(Columns)
SELECT
EventType = #x.value('(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(256)'),
UserName = #x.value('(/EVENT_INSTANCE/ObjectName)[1]','nvarchar(256)'),
LoginName = #x.value('(/EVENT_INSTANCE/LoginName)[1]', 'nvarchar(512)'),
StartTime = #x.value('(/EVENT_INSTANCE/PostTime)[1]', 'datetime');
END
However, if you are just trying to audit this data after the fact, you can also pull this information from the default trace, if you poll frequently enough.
DECLARE #path NVARCHAR(260);
SELECT #path = REVERSE(SUBSTRING(REVERSE([path]),
CHARINDEX(CHAR(92), REVERSE([path])), 260)) + N'log.trc'
FROM sys.traces WHERE is_default = 1;
SELECT EventType = CASE EventSubClass WHEN 3 THEN 'CREATE_USER'
WHEN 4 THEN 'DROP_USER' END, TargetUserName, LoginName, StartTime
FROM sys.fn_trace_gettable(#path, DEFAULT)
WHERE EventClass = 109 -- Create DB User Event
AND DatabaseName = N'your_database_name'
ORDER BY StartTime DESC;
This will get adds and drops, and you're supposed to be able to tell from the EventSubClass which event it was, but my experience is not matching with the documentation - I get 3 for Add, 4 for Drop, but they say 1 is Add, 2 is Drop, 3 is grant access, and 4 is revoke access. shrug

Resources