How to Allow Non-Root Users to Restart Systemd Services for Jenkins Deployment


36 views

When automating deployments with Jenkins, we often need to restart services after updating application files. The problem arises when Jenkins (running as a non-root user) needs to control systemd services that typically require root privileges.

In this scenario, we have:

- A Spring Boot application packaged as myapp.jar
- A systemd service unit at /etc/systemd/system/myapp.service
- A dedicated 'myapp' user owning the application files
- Jenkins configured to SSH as 'myapp' for deployments

The most secure approach is to create a Polkit rule that grants specific service control permissions to the 'myapp' user:

# Create a new polkit rule file
sudo nano /etc/polkit-1/rules.d/10-myapp.rules

Add this content:

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units" && 
        action.lookup("unit") == "myapp.service" && 
        subject.user == "myapp") {
        return polkit.Result.YES;
    }
});

If Polkit isn't available, we can create a sudoers entry with precise permissions:

# Create a new sudoers file fragment
sudo nano /etc/sudoers.d/myapp-service-control

Add this content:

myapp ALL=(root) NOPASSWD: /bin/systemctl start myapp
myapp ALL=(root) NOPASSWD: /bin/systemctl stop myapp
myapp ALL=(root) NOPASSWD: /bin/systemctl restart myapp

Test the configuration by switching to the 'myapp' user:

sudo -u myapp systemctl restart myapp

If successful, Jenkins can now execute these commands via SSH without requiring root access.

Here's how to implement this in a Jenkins pipeline:

pipeline {
    agent any
    stages {
        stage('Deploy') {
            steps {
                sshagent(['myapp-ssh-key']) {
                    sh '''
                        ssh myapp@myserver "sudo systemctl stop myapp"
                        scp target/myapp.jar myapp@myserver:/home/myapp/
                        ssh myapp@myserver "sudo systemctl start myapp"
                    '''
                }
            }
        }
    }
}

When implementing this solution:

  • Always use dedicated service accounts (not shared with human users)
  • Restrict SSH access to specific commands if possible
  • Regularly audit service control permissions
  • Consider using configuration management tools for more complex scenarios

When deploying Spring Boot applications as systemd services, a common security dilemma arises: how to enable CI/CD systems (like Jenkins) to manage service lifecycle operations without granting full sudo privileges. The standard approach of requiring root for systemctl commands creates unnecessary security exposure.

Modern systemd versions (≥ 217) include granular permission controls through polkit policies. We can leverage this to allow specific users to manage defined services. Here's how to implement it for our myapp scenario:

# Create a polkit rule file
sudo nano /etc/polkit-1/rules.d/10-myapp.rules

Add this JavaScript-based policy rule:

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.systemd1.manage-units" &&
        action.lookup("unit") == "myapp.service" &&
        subject.user == "myapp") {
        return polkit.Result.YES;
    }
});

For older systems without polkit support, we can create a granular sudo exception:

# Create a dedicated sudoers file
sudo visudo -f /etc/sudoers.d/myapp-management

# Add these specific permissions
myapp ALL=(root) NOPASSWD: /bin/systemctl start myapp
myapp ALL=(root) NOPASSWD: /bin/systemctl stop myapp
myapp ALL=(root) NOPASSWD: /bin/systemctl restart myapp

With permissions configured, here's how to implement the Jenkins pipeline:

pipeline {
    agent any
    stages {
        stage('Deploy') {
            steps {
                sshagent(['myapp-ssh-key']) {
                    sh '''
                        ssh -o StrictHostKeyChecking=no myapp@myserver \
                        "sudo systemctl stop myapp && \
                        cp backend-1.0-SNAPSHOT.jar /home/myapp/ && \
                        sudo systemctl start myapp"
                    '''
                }
            }
        }
    }
}
  • Always use dedicated service accounts (never reuse personal accounts)
  • Regularly audit polkit/sudoers rules
  • Consider implementing two-factor authentication for Jenkins deployments
  • Use signed artifacts to prevent malicious jar replacement

Ensure your myapp.service includes proper security directives:

[Unit]
Description=My Spring Boot Application
After=syslog.target

[Service]
User=myapp
Group=myapp
WorkingDirectory=/home/myapp
ExecStart=/usr/bin/java -jar /home/myapp/myapp.jar
Restart=on-failure

# Security hardening
PrivateTmp=true
ProtectSystem=full
NoNewPrivileges=true