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


1 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