Martijn de Milliano
Martijn de Milliano

Reputation: 3958

Multiple file.line in single state in Salt

I would like to have a Salt state for managing my SSH file. This requires multiple file.line operations. How can I do this?

UPDATE: See bottom of the question for my current workaround

What I have is this:

Secure SSH:
  file:
   - name: /etc/ssh/sshd_config
   - line:
     - match: "^PasswordAuthentication "
     - content: "PasswordAuthentication no"
     - mode: ensure
   - line:
     - match: "^PubkeyAuthentication "
     - content: "PubkeyAuthentication yes"
     - mode: ensure
   - line:
     - match: "^Port "
     - content: "Port 8888"
     - mode: ensure
  service.running:
    - name: sshd
    - watch:
      - file: /etc/ssh/sshd_config

but this fails with

    Data failed to compile:
----------
    No function declared in state 'file' in SLS u'xyz'

Actually my first attempt was this:

Secure SSH:
  file.line:
    - name: /etc/ssh/sshd_config
    - match: "^PasswordAuthentication "
    - content: "PasswordAuthentication no"
    - mode: ensure
  file.line:
    - name: /etc/ssh/sshd_config
    - match: "^PubkeyAuthentication "
    - content: "PubkeyAuthentication yes"
    - mode: ensure
  file.line:
    - name: /etc/ssh/sshd_config
    - match: "^Port "
    - content: "Port 8888"
    - mode: ensure
  service.running:
    - name: sshd
    - watch:
      - file: /etc/ssh/sshd_config

but this fails with

    Data failed to compile:
----------
    Rendering SLS 'base:xyz' failed: Conflicting ID 'file.line'

I understand this error because every state function is a dictionary key, but it does look very clean.

The Salt documentation is very unhelpful in this because it does not say anything about what to do when just maybe you want to modify multiple things to one file, and it conveniently only gives very trivial examples in its documentation.

UPDATE: I got it to work by using a separate state for each line (and I also changed file.line to file.replace but that was another issue). I think this is rather unwieldy plus isn't the service reloaded after every step?

Disallow SSH password authentication:
  file.replace:
    - name: /etc/ssh/sshd_config
    - pattern: ^PasswordAuthentication .*
    - repl: PasswordAuthentication no
    - append_if_not_found: True
  service.running:
    - name: sshd
    - watch:
      - file: /etc/ssh/sshd_config

Allow SSH public key authentication:
  file.replace:
    - name: /etc/ssh/sshd_config
    - pattern: ^PubkeyAuthentication .*
    - repl: PubkeyAuthentication yes
    - append_if_not_found: True
  service.running:
    - name: sshd
    - watch:
      - file: /etc/ssh/sshd_config

Set SSH port:
  file.replace:
    - name: /etc/ssh/sshd_config
    - pattern: ^Port .*
    - repl: Port 8888
    - append_if_not_found: True
  service.running:
    - name: sshd
    - watch:
      - file: /etc/ssh/sshd_config

Upvotes: 4

Views: 7173

Answers (2)

gtmanfred
gtmanfred

Reputation: 593

  1. I would recommend checking out listen instead of watch. Watch will restart sshd 3 times, once after each time the file is changed.

    If you use listen, it will only restart it once at the very end. But you have to do

  2. Put the service.running at the very end with it's own stateid and listen to all of the changes.

    Disallow SSH password authentication:
      file.replace:
        - name: /etc/ssh/sshd_config
        - pattern: ^PasswordAuthentication .*
        - repl: PasswordAuthentication no
        - append_if_not_found: True
    
    Allow SSH public key authentication:
      file.replace:
        - name: /etc/ssh/sshd_config
        - pattern: ^PubkeyAuthentication .*
        - repl: PubkeyAuthentication yes
        - append_if_not_found: True
    
    Set SSH port:
      file.replace:
        - name: /etc/ssh/sshd_config
        - pattern: ^Port .*
        - repl: Port 8888
        - append_if_not_found: True
    
    Start SSHD:
      service.running:
        - name: sshd
        - listen:
          - file: /etc/ssh/sshd_config
    
  3. You might also find it worthwhile to check out the augeas state. It makes making changes like this a lot easier and look better in the state files.

    sshd_config:
      augeas.change:
        - context: /files/etc/ssh/sshd_config
        - changes:
          - set Port 8888
          - set PasswordAuthentication yes
          - set PubkeyAuthentication yes
      service.running:
        - name: sshd
        - listen:
          - augeas: sshd_config
    

Upvotes: 2

ahus1
ahus1

Reputation: 5932

Separating the file.replace to multiple states is the way to go.

To avoid redundancy you should move the service.running to its own state as well. Plus: when using watch (or watch_in) you'll need to specify the name of state you are watching after the file: part.

The result will look like this:

Disallow SSH password authentication:
  file.replace:
    - name: /etc/ssh/sshd_config
    - pattern: ^PasswordAuthentication .*
    - repl: PasswordAuthentication no
    - append_if_not_found: True
    - watch_in:
      - service: ssh_service

Allow SSH public key authentication:
  file.replace:
    - name: /etc/ssh/sshd_config
    - pattern: ^PubkeyAuthentication .*
    - repl: PubkeyAuthentication yes
    - append_if_not_found: True
    - watch_in:
      - service: ssh_service

Set SSH port:
  file.replace:
    - name: /etc/ssh/sshd_config
    - pattern: ^Port .*
    - repl: Port 8888
    - append_if_not_found: True
    - watch_in:
      - service: ssh_service

ssh_service:
  service.running:
    - name: sshd

Upvotes: 3

Related Questions