How to Execute sp_start_job from a Stored Procedure with EXECUTE AS in SQL Server


4 views

When implementing automated job execution in SQL Server, developers often encounter the classic security vs functionality trade-off. The direct approach of calling msdb.dbo.sp_start_job requires granting users elevated permissions, which violates security best practices. Here's a deeper look at this scenario:

-- Direct approach (security risk)
GRANT EXECUTE ON msdb.dbo.sp_start_job TO [AppUser];

The WITH EXECUTE AS clause creates an execution context switch, but it doesn't automatically grant the proxy account necessary permissions. The error message indicates the impersonated account (agentProxy) lacks execute rights on sp_start_job.

Here's the proper implementation that maintains security while enabling job execution:

-- 1. Create proxy credential (if not exists)
USE master;
GO
CREATE LOGIN [agentProxy] WITH PASSWORD = 'ComplexP@ssw0rd!';
GO

-- 2. Grant required permissions in msdb
USE msdb;
GO
CREATE USER [agentProxy] FOR LOGIN [agentProxy];
GO
GRANT EXECUTE ON [dbo].[sp_start_job] TO [agentProxy];
GO

-- 3. Create the wrapper procedure
USE YourAppDatabase;
GO
CREATE PROCEDURE dbo.StartAgentJob
    @JobName NVARCHAR(128)
    WITH EXECUTE AS 'agentProxy'
AS
BEGIN
    -- Validate job name to prevent SQL injection
    IF EXISTS (SELECT 1 FROM msdb.dbo.sysjobs WHERE name = @JobName)
    BEGIN
        EXEC msdb.dbo.sp_start_job @JobName;
        RETURN 0; -- Success
    END
    RETURN 1; -- Job not found
END
GO

-- 4. Grant app users execute on wrapper only
GRANT EXECUTE ON dbo.StartAgentJob TO [AppUser];

For enterprise scenarios, consider these enhancements:

-- Add job execution logging
ALTER PROCEDURE dbo.StartAgentJob
    @JobName NVARCHAR(128),
    @Caller SYSNAME = NULL
WITH EXECUTE AS 'agentProxy'
AS
BEGIN
    BEGIN TRY
        DECLARE @JobId UNIQUEIDENTIFIER;
        
        SELECT @JobId = job_id 
        FROM msdb.dbo.sysjobs 
        WHERE name = @JobName;
        
        IF @JobId IS NULL
        BEGIN
            INSERT INTO dbo.JobExecutionLog(JobName, Status, ErrorMessage)
            VALUES(@JobName, 'Failed', 'Job not found');
            RETURN 1;
        END
        
        EXEC msdb.dbo.sp_start_job @JobName;
        
        INSERT INTO dbo.JobExecutionLog(JobName, Status, StartTime, Caller)
        VALUES(@JobName, 'Started', GETDATE(), ISNULL(@Caller,SUSER_SNAME()));
        
        RETURN 0;
    END TRY
    BEGIN CATCH
        INSERT INTO dbo.JobExecutionLog(JobName, Status, ErrorMessage)
        VALUES(@JobName, 'Failed', ERROR_MESSAGE());
        RETURN 2;
    END CATCH
END

For higher security environments, consider certificate signing:

-- Create certificate in your app database
USE YourAppDatabase;
GO
CREATE CERTIFICATE JobExecutorCert
   ENCRYPTION BY PASSWORD = 'CertP@ssw0rd!'
   WITH SUBJECT = 'Job Execution Certificate';
GO

-- Create procedure and sign it
CREATE PROCEDURE dbo.StartAgentJob
    @JobName NVARCHAR(128)
AS
BEGIN
    EXEC msdb.dbo.sp_start_job @JobName;
END
GO

ADD SIGNATURE TO dbo.StartAgentJob
BY CERTIFICATE JobExecutorCert
WITH PASSWORD = 'CertP@ssw0rd!';
GO

-- Copy certificate to msdb and create user
USE msdb;
GO
CREATE CERTIFICATE JobExecutorCert
FROM FILE = 'C:\path\to\your\certificate.cer';
GO

CREATE USER JobExecutorUser FROM CERTIFICATE JobExecutorCert;
GO
GRANT EXECUTE ON [dbo].[sp_start_job] TO JobExecutorUser;
GO

When implementing job execution functionality in SQL Server 2005 (and later versions), developers often encounter permission issues with sp_start_job. The core problem occurs when trying to delegate job execution rights without granting direct access to msdb procedures.

The WITH EXECUTE AS clause establishes execution context, but doesn't automatically grant cross-database permissions. The error message clearly indicates the impersonated account lacks execute permissions on the target stored procedure in msdb.

-- This will fail without proper msdb permissions
CREATE PROCEDURE dbo.StartAgentJob 
    WITH EXECUTE AS 'agentProxy'
AS
BEGIN
    EXEC msdb.dbo.sp_start_job N'RunThisJob';
END

Here's the proper way to implement this securely:

-- First, grant necessary permissions in msdb
USE [msdb]
GO
GRANT EXECUTE ON [dbo].[sp_start_job] TO [agentProxy]
GO

-- Then create the wrapper procedure
USE [YourAppDB]
GO
CREATE PROCEDURE dbo.StartAgentJob
    @JobName NVARCHAR(128)
    WITH EXECUTE AS 'agentProxy'
AS
BEGIN
    -- Input validation
    IF NOT EXISTS (SELECT 1 FROM msdb.dbo.sysjobs WHERE name = @JobName)
    BEGIN
        RAISERROR('Specified job does not exist', 16, 1)
        RETURN
    END
    
    EXEC msdb.dbo.sp_start_job @JobName;
END
GO

For even better security, consider certificate-based solutions:

-- Create certificate in application database
USE [YourAppDB]
CREATE CERTIFICATE JobExecutionCert
   ENCRYPTION BY PASSWORD = 'ComplexPassword123!'
   WITH SUBJECT = 'Certificate for job execution';

-- Create procedure signed by certificate
CREATE PROCEDURE dbo.StartAgentJob
    @JobName NVARCHAR(128)
AS
BEGIN
    EXEC msdb.dbo.sp_start_job @JobName;
END
GO

ADD SIGNATURE TO dbo.StartAgentJob
   BY CERTIFICATE JobExecutionCert
   WITH PASSWORD = 'ComplexPassword123!';

-- Copy certificate to msdb and create login
USE [msdb]
CREATE CERTIFICATE JobExecutionCert
   FROM BINARY = ... -- Binary from YourAppDB
GO

CREATE LOGIN JobExecutionLogin FROM CERTIFICATE JobExecutionCert;
GRANT EXECUTE ON [dbo].[sp_start_job] TO JobExecutionLogin;
  • Always validate job names before execution
  • Implement logging of job execution attempts
  • Consider adding parameter for job completion wait time
  • Use module signing for most secure implementation