Repository: openpubkey/opkssh
Pull request #197: Honor the INSTALL_DIR Variable and openSUSE/SLES support
Author: login: SweBarre
Labels: name: enhancement
description: New feature or request
Created: 2025-05-15T18:07:58Z | Merged: 2025-05-20T16:07:46Z

This PR consists of four changes to the install-linux.sh script: 
- [Honor the INSTALL_DIR variable](https://github.com/openpubkey/opkssh/commit/d9b7909e7a583ee0514f5a19d1cf63265606c1f8)
- [Adds support for openSUSE and SUSE Linux](https://github.com/openpubkey/opkssh/commit/1ec5014669adf87f27846a08de086030c9f9a8f0)
- [Checks if opssh-server is installed](https://github.com/openpubkey/opkssh/pull/197/commits/93ffc6edebce1e4873a96d9b51f549d4a5693f64)
- [Updates the decision on where to place the opkssh sshd configuraiton](https://github.com/openpubkey/opkssh/commit/c71b846b3246a9e712ba2a79656696316d83bce7) 
    openSUSE Tumbleweed uses UsrEtc (hermetic-usr) so there is no
    /etc/ssh/sshd_config file so the install script failed.
    determine where opkssh configuration should be added:
```    
    +------------------------------------------------+
    | Does /etc/ssh/sshd_config exist?               |---------------------|
    +------------------------------------------------+    No               |
             | Yes                                                         |
             v                                                             |
    +------------------------------------------+                           |
    | Contains line:                           |                           |
    | "Include /etc/ssh/sshd_config.d/*.conf"? |                           |
    +------------------------------------------+                           |
             |                          |                                  |
             | No                       | Yes                              |
             |                          v                                  |
             |                 +-----------------------------------+       |
             |                 | Contains AuthorizedKeysCommand    |------>|
             |                 | or AuthorizedKeysCommandUser?     |   No  |
             |                 +-----------------------------------+       |
             |                       |                                     |
             |                       | Yes                                 |
             |                       v                                     |
             |     --------------------------------+                       |
             |    | Any .conf file in              |                       |
             |    | /etc/ssh/sshd_config.d/        |---------------------->|
             |    | with AuthorizedKeysCommand?    |    Yes                |
             |    +--------------------------------+                       |
             |                 |                                           |
             |                 | No                                        |
             |                 |                                           |
             |                 |                                           |
             v                 v                                           v
      +--------------------------------+   +-----------------------------------+
      |     Update sshd_config         |   | Update sshd_config.d/*.conf       |
      +--------------------------------+   +-----------------------------------+
```    
    Links:
     - https://en.opensuse.org/openSUSE:Packaging_UsrEtc
     - https://github.com/uapi-group/specifications/blob/main/specs/configuration_files_specification.md







Diff:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 16a7eec2..020b554e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -56,7 +56,7 @@ jobs:
     strategy:
       matrix:
         runs_on: [ubuntu-latest, ubuntu-24.04-arm]
-        os: [ubuntu, centos, arch]
+        os: [ubuntu, centos, arch, opensuse]
         exclude:
           - runs_on: ubuntu-24.04-arm
             os: arch
diff --git a/README.md b/README.md
index 0b63be1a..cfb049ed 100644
--- a/README.md
+++ b/README.md
@@ -180,6 +180,7 @@ Second, we use the `AuthorizedKeysCommand` configuration option in `sshd_config`
 | Linux            | ✅        | ✅     |  Ubuntu 24.04.1 LTS    | -                       |
 | Linux            | ✅        | ✅     |  Centos 9              | -                       |
 | Linux            | ✅        | ✅     |  Arch Linux            | -                       |
+| Linux            | ✅        | ✅     |  openSUSE Tumbleweed   | -                       |
 | OSX              | ❌        | ❌     |  -                     | Likely                  |
 | Windows11        | ❌        | ❌     |  -                     | Likely                  |
 
diff --git a/scripts/install-linux.sh b/scripts/install-linux.sh
index eb56347c..86f7b320 100644
--- a/scripts/install-linux.sh
+++ b/scripts/install-linux.sh
@@ -9,6 +9,8 @@ elif [ -f /etc/debian_version ]; then
     OS_TYPE="debian"
 elif [ -f /etc/arch-release ]; then
     OS_TYPE="arch"
+elif [ -f /etc/os-release ] && grep -q '^ID_LIKE=.*suse' /etc/os-release; then
+    OS_TYPE="suse"
 else
     echo "Unsupported OS type."
     exit 1
@@ -104,12 +106,53 @@ if ! command -v wget &> /dev/null; then
         fi
     elif [ "$OS_TYPE" == "arch" ]; then
         echo "sudo pacman -S wget"
+    elif [ "$OS_TYPE" == "suse" ]; then
+        echo "sudo zypper install wget"
     else
         echo "Unsupported OS type."
     fi
     exit 1
 fi
 
+# Ensure openSSH server is installed
+case "$OS_TYPE" in
+    redhat)
+        if ! rpm -q openssh-server &>/dev/null; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo dnf install openssh-server" >&2
+            exit 1
+        fi
+        ;;
+    debian)
+        if ! dpkg -l | grep -q '^ii.*openssh-server'; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo apt install openssh-server" >&2
+            exit 1
+        fi
+        ;;
+    arch)
+        if ! pacman -Q openssh &>/dev/null; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo pacman -S openssh" >&2
+            exit 1
+        fi
+        ;;
+    suse)
+        if ! rpm -q openssh-server &>/dev/null; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo zypper install openssh-server" >&2
+            exit 1
+        fi
+        ;;
+esac
+
+# Ensure OpenSSH server configuration targets exists
+if [[ ! -f /etc/ssh/sshd_config && ! -d /etc/ssh/sshd_config.d ]]; then
+    echo "Neither /etc/ssh/sshd_config nor /etc/ssh/sshd_config.d exists." >&2
+    exit 1
+fi
+
+
 # Checks if the group and user used by the AuthorizedKeysCommand exists if not creates it
 if ! getent group "$AUTH_CMD_GROUP" >/dev/null; then
     groupadd --system "$AUTH_CMD_GROUP"
@@ -296,12 +339,16 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
         echo "$PROVIDER_GITLAB" >> /etc/opk/providers
     fi
 
-    AUTH_KEY_CMD="AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t"
+    AUTH_KEY_CMD="AuthorizedKeysCommand ${INSTALL_DIR}/opkssh verify %u %k %t"
     AUTH_KEY_USER="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
 
     # Add the directives in the correct configuration, taking priorities into account
-    if ! grep -q '^Include /etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
-    || ! grep -q '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf ; then
+    if [[ -f /etc/ssh/sshd_config ]] && \
+    { ! grep -Fxq 'Include /etc/ssh/sshd_config.d/*.conf' /etc/ssh/sshd_config || \
+      { grep -Eq '^AuthorizedKeysCommand|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config && \
+       ! grep -Eq '^AuthorizedKeysCommand|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf 2>/dev/null; \
+      }; \
+    }; then
         # The directives in 'sshd_config' are active
         sed -i '/^AuthorizedKeysCommand /s/^/#/' /etc/ssh/sshd_config
         sed -i '/^AuthorizedKeysCommandUser /s/^/#/' /etc/ssh/sshd_config
@@ -324,11 +371,15 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
             echo "  Cannot create configuration with higher priority. Remove $active_config or rerun the script with the --overwrite-config flag to overwrite"
             exit 1
         else
-            # Create a new config file with higher priority
-            prefix=$(basename "$active_config" | grep -o '^[0-9]*')
-            new_prefix=$((prefix - 1))
+            if [[ -z "$active_config" ]]; then
+                # No active configuration found, let's set a default prefix
+                new_prefix=60
+            else
+                # Create a new config file with higher priority
+                prefix=$(basename "$active_config" | grep -o '^[0-9]*')
+                new_prefix=$((prefix - 1))
+            fi
             new_config="/etc/ssh/sshd_config.d/${new_prefix}-$opk_config_suffix"
-
             echo "$AUTH_KEY_CMD" > "$new_config"
             echo "$AUTH_KEY_USER" >> "$new_config"
         fi
@@ -337,7 +388,7 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
     if [ "$RESTART_SSH" = true ]; then
         if [ "$OS_TYPE" == "debian" ]; then
             systemctl restart ssh
-        elif [ "$OS_TYPE" == "redhat" ] || [ "$OS_TYPE" == "arch" ]; then
+        elif [ "$OS_TYPE" == "redhat" ] || [ "$OS_TYPE" == "arch" ] || [ "$OS_TYPE" == "suse" ]; then
             systemctl restart sshd
         else
             echo "  Unsupported OS type."
@@ -353,7 +404,7 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
             touch "$SUDOERS_PATH"
             chmod 440 "$SUDOERS_PATH"
         fi
-        SUDOERS_RULE_READ_HOME="$AUTH_CMD_USER ALL=(ALL) NOPASSWD: /usr/local/bin/opkssh readhome *"
+        SUDOERS_RULE_READ_HOME="$AUTH_CMD_USER ALL=(ALL) NOPASSWD: ${INSTALL_DIR}/opkssh readhome *"
         if ! grep -qxF "$SUDOERS_RULE_READ_HOME" "$SUDOERS_PATH"; then
             echo "  Adding sudoers rule for $AUTH_CMD_USER..."
             echo "# This allows opkssh to call opkssh readhome <username> to read the user's policy file in /home/<username>/auth_id" >> "$SUDOERS_PATH"
diff --git a/test/integration/ssh_server/opensuse_opkssh.Dockerfile b/test/integration/ssh_server/opensuse_opkssh.Dockerfile
new file mode 100644
index 00000000..c312c8fb
--- /dev/null
+++ b/test/integration/ssh_server/opensuse_opkssh.Dockerfile
@@ -0,0 +1,76 @@
+# Stage 1: Build the Go binary
+FROM golang:1.23 as builder
+
+# Set destination for COPY
+WORKDIR /app
+
+# Download Go modules
+COPY go.mod go.sum ./
+RUN go mod download
+
+# Copy our repo
+COPY . ./
+
+# Copy the source code and build the binary
+ARG ISSUER_PORT="9998"
+RUN go build -v -o opksshbuild
+
+# Stage 2: Create a minimal openSUSE-Tumbleweed-based image
+FROM opensuse/tumbleweed:latest
+# Install dependencies required for runtime (e.g., SSH server)
+RUN zypper refresh && \
+    zypper --non-interactive install sudo openssh-server openssh-clients telnet wget jq && \
+    zypper clean --all && \
+    rm /var/log/zypp/history && \
+    rm /var/log/zypper.log
+
+# Source:
+# https://medium.com/@ratnesh4209211786/simplified-ssh-server-setup-within-a-docker-container-77eedd87a320
+#
+# Create an SSH user named "test". Make it a sudoer
+RUN useradd -rm -d /home/test -s /bin/bash -g root -u 480 test
+# Set password to "test"
+RUN  echo "test:test" | chpasswd
+
+# Make it so "test" user does not need to present password when using sudo
+# Source: https://askubuntu.com/a/878705
+RUN echo "test ALL=(ALL:ALL) NOPASSWD: ALL" | tee /etc/sudoers.d/test
+
+# Create unprivileged user named "test2" 
+RUN useradd -rm -d /home/test2 -s /bin/bash -u 481 test2
+# Set password to "test"
+RUN  echo "test2:test" | chpasswd
+
+# Allow SSH access
+RUN mkdir /var/run/sshd
+
+# Expose SSH server so we can ssh in from the tests
+EXPOSE 22
+
+WORKDIR /app
+
+# Copy binary and install script from builder
+COPY --from=builder /app/opksshbuild ./opksshbuild
+COPY --from=builder /app/scripts/install-linux.sh install-linux.sh
+
+# Run install script to install/configure opkssh
+RUN chmod +x install-linux.sh
+RUN bash ./install-linux.sh --install-from=opksshbuild --no-sshd-restart
+
+RUN opkssh --version
+RUN ls -l /usr/local/bin
+RUN printenv PATH
+
+ARG ISSUER_PORT="9998"
+RUN echo "http://oidc.local:${ISSUER_PORT}/ web oidc_refreshed" >> /etc/opk/providers
+
+# Add integration test user as allowed email in policy (this directly tests
+# policy "add" command)
+ARG BOOTSTRAP_POLICY
+RUN if [ -n "$BOOTSTRAP_POLICY" ] ; then opkssh add "test" "test-user@zitadel.ch" "http://oidc.local:${ISSUER_PORT}/"; else echo "Will not init policy" ; fi
+
+# Generate SSH host keys
+RUN ssh-keygen -A
+
+# Start the SSH server on container startup
+CMD ["/usr/sbin/sshd", "-D"]
diff --git a/test/integration/ssh_server/ssh_server.go b/test/integration/ssh_server/ssh_server.go
index 93c5f745..f0f056fe 100644
--- a/test/integration/ssh_server/ssh_server.go
+++ b/test/integration/ssh_server/ssh_server.go
@@ -51,7 +51,9 @@ func RunOpkSshContainer(ctx context.Context, issuerHostIp string, issuerPort str
 	} else if osType == "centos" {
 		dockerFile = filepath.Join("test", "integration", "ssh_server", "centos_opkssh.Dockerfile")
 	} else if osType == "arch" {
-    	dockerFile = filepath.Join("test", "integration", "ssh_server", "arch_opkssh.Dockerfile")
+		dockerFile = filepath.Join("test", "integration", "ssh_server", "arch_opkssh.Dockerfile")
+	} else if osType == "opensuse" {
+		dockerFile = filepath.Join("test", "integration", "ssh_server", "opensuse_opkssh.Dockerfile")
 	} else {
 		return nil, fmt.Errorf("unsupported OS type: %s", osType)
 	}


Reviews:
user: login: EthanHeilman
body: This looks great. Two minor comments and then I'll merge
- I think there is one minor error case to catch below, unless I misunderstood.
- Some of the commit messages have valuable documentation, can you include them in the PR description


state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-19T17:45:28Z
user: login: SweBarre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T18:08:47Z
user: login: EthanHeilman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-19T18:37:18Z
user: login: SweBarre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-19T18:57:10Z
user: login: SweBarre
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-20T06:40:44Z
user: login: EthanHeilman
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-20T14:46:22Z
user: login: EthanHeilman
body: LGTM, fix small typo and I can merge
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-20T14:47:54Z

Review comments:
diff_hunk: @@ -296,12 +300,16 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
         echo "$PROVIDER_GITLAB" >> /etc/opk/providers
     fi
 
-    AUTH_KEY_CMD="AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t"
+    AUTH_KEY_CMD="AuthorizedKeysCommand ${INSTALL_DIR}/opkssh verify %u %k %t"
     AUTH_KEY_USER="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
 
     # Add the directives in the correct configuration, taking priorities into account
-    if ! grep -q '^Include /etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
-    || ! grep -q '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf ; then
+    if [[ -f /etc/ssh/sshd_config ]] && \
path: scripts/install-linux.sh
user: login: EthanHeilman
body: If I'm reading this correct, this code has the behavior that If `/etc/ssh/sshd_config` can not be found it jumps to the else and tries to create a `/etc/ssh/sshd_config.d/*` file. We should cover the case that ``/etc/ssh/sshd_config` doesn't exist and ``/etc/ssh/sshd_config.d/` doesn't exist. What happens if sshd isn't installed?

We should probably fail early and inform the user they need to install sshd or that no config can be found in the `/etc/ssh/` directory

created_at: 2025-05-19T17:22:14Z
author_association: MEMBER
diff_hunk: @@ -296,12 +300,16 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
         echo "$PROVIDER_GITLAB" >> /etc/opk/providers
     fi
 
-    AUTH_KEY_CMD="AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t"
+    AUTH_KEY_CMD="AuthorizedKeysCommand ${INSTALL_DIR}/opkssh verify %u %k %t"
     AUTH_KEY_USER="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
 
     # Add the directives in the correct configuration, taking priorities into account
-    if ! grep -q '^Include /etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
-    || ! grep -q '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf ; then
+    if [[ -f /etc/ssh/sshd_config ]] && \
path: scripts/install-linux.sh
user: login: SweBarre
body: That makes sense, I could had that check as well.
I think a better way to check that is to use the package manager to check if that package is installed or not early in the script.
Something like (need to test it though):
```
check_openssh_installed() {
    case "$OS_TYPE" in
        redhat)
            if rpm -q openssh-server &>/dev/null; then
                return 0
            else
                echo "OpenSSH server is NOT installed."
                echo "To install it, run: sudo dnf install openssh-server"
                exit 1
            fi
            ;;
        debian)
            if dpkg -l | grep -q '^ii.*openssh-server'; then
                return 0
            else
                echo "OpenSSH server is NOT installed."
                echo "To install it, run: sudo apt install openssh-server"
                exit 1
            fi
            ;;
        arch)
            if pacman -Q openssh &>/dev/null; then
                return 0
            else
                echo "OpenSSH server is NOT installed."
                echo "To install it, run: sudo pacman -S openssh"
                exit 1
            fi
            ;;
        suse)
            if rpm -q openssh-server &>/dev/null; then
                return 0
            else
                echo "OpenSSH server is NOT installed."
                echo "To install it, run: sudo zypper install openssh"
                exit 1
            fi
            ;;
    esac
}
```
created_at: 2025-05-19T18:08:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,12 +300,16 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
         echo "$PROVIDER_GITLAB" >> /etc/opk/providers
     fi
 
-    AUTH_KEY_CMD="AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t"
+    AUTH_KEY_CMD="AuthorizedKeysCommand ${INSTALL_DIR}/opkssh verify %u %k %t"
     AUTH_KEY_USER="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
 
     # Add the directives in the correct configuration, taking priorities into account
-    if ! grep -q '^Include /etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
-    || ! grep -q '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf ; then
+    if [[ -f /etc/ssh/sshd_config ]] && \
path: scripts/install-linux.sh
user: login: EthanHeilman
body: That makes sense. 

I just want to make sure if those config directories don't exist for some strange reason, the install fails as it would be very difficult to debug after the fact if it succeeds despite sshd config actually living somewhere else.
created_at: 2025-05-19T18:37:18Z
author_association: MEMBER
diff_hunk: @@ -296,12 +300,16 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
         echo "$PROVIDER_GITLAB" >> /etc/opk/providers
     fi
 
-    AUTH_KEY_CMD="AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t"
+    AUTH_KEY_CMD="AuthorizedKeysCommand ${INSTALL_DIR}/opkssh verify %u %k %t"
     AUTH_KEY_USER="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
 
     # Add the directives in the correct configuration, taking priorities into account
-    if ! grep -q '^Include /etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
-    || ! grep -q '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf ; then
+    if [[ -f /etc/ssh/sshd_config ]] && \
path: scripts/install-linux.sh
user: login: SweBarre
body: Fair enough,
I could add both checks and a give a  explanatory error message if the package is installed but both the config file directory are missing. That way the install script catches the weird scenario when the config file exists but the openSSH server isn't installed.
created_at: 2025-05-19T18:57:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -296,12 +300,16 @@ if command -v $INSTALL_DIR/$BINARY_NAME &> /dev/null; then
         echo "$PROVIDER_GITLAB" >> /etc/opk/providers
     fi
 
-    AUTH_KEY_CMD="AuthorizedKeysCommand /usr/local/bin/opkssh verify %u %k %t"
+    AUTH_KEY_CMD="AuthorizedKeysCommand ${INSTALL_DIR}/opkssh verify %u %k %t"
     AUTH_KEY_USER="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
 
     # Add the directives in the correct configuration, taking priorities into account
-    if ! grep -q '^Include /etc/ssh/sshd_config\.d/\*\.conf' /etc/ssh/sshd_config \
-    || ! grep -q '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' /etc/ssh/sshd_config.d/*.conf ; then
+    if [[ -f /etc/ssh/sshd_config ]] && \
path: scripts/install-linux.sh
user: login: SweBarre
body: @EthanHeilman , I've updated the PR description and added the openssh-server package check and configuration check to the script.

created_at: 2025-05-20T06:40:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -104,12 +106,53 @@ if ! command -v wget &> /dev/null; then
         fi
     elif [ "$OS_TYPE" == "arch" ]; then
         echo "sudo pacman -S wget"
+    elif [ "$OS_TYPE" == "suse" ]; then
+        echo "sudo zypper install wget"
     else
         echo "Unsupported OS type."
     fi
     exit 1
 fi
 
+# Ensure openSSH server is installed
+case "$OS_TYPE" in
+    redhat)
+        if ! rpm -q openssh-server &>/dev/null; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo dnf install openssh-server" >&2
+            exit 1
+        fi
+        ;;
+    debian)
+        if ! dpkg -l | grep -q '^ii.*openssh-server'; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo apt install openssh-server" >&2
+            exit 1
+        fi
+        ;;
+    arch)
+        if ! pacman -Q openssh &>/dev/null; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo pacman -S openssh" >&2
+            exit 1
+        fi
+        ;;
+    suse)
+        if ! rpm -q openssh-server &>/dev/null; then
+            echo "OpenSSH server is NOT installed." >&2
+            echo "To install it, run: sudo zypper install openssh-server" >&2
+            exit 1
+        fi
+        ;;
+esac
+
+# Ensure openSSH configration targets exists
path: scripts/install-linux.sh
user: login: EthanHeilman
body: Minor typo

```suggestion
# Ensure OpenSSH server configuration targets exists
```
created_at: 2025-05-20T14:46:22Z
author_association: MEMBER

PR comments:
user: login: EthanHeilman
created_at: 2025-05-15T22:21:31Z
body: Thanks for this
author_association: MEMBER
user: login: EthanHeilman
created_at: 2025-05-16T15:04:39Z
body: @SweBarre If we are going support SUSE in the install script can you add SUSE to set of linux flavors we test in the integration test matrix?

1. Add SUSE to the test matrix in the github-action: https://github.com/openpubkey/opkssh/blob/722f7bc10d64ea3db2a4f586a871b5228145e48f/.github/workflows/ci.yml#L51-L56

2. Then create a docker file for the SUSE ssh server. You can use the [arch](https://github.com/openpubkey/opkssh/blob/main/test/integration/ssh_server/arch_opkssh.Dockerfile)or [centos docker](https://github.com/openpubkey/opkssh/blob/main/test/integration/ssh_server/centos_opkssh.Dockerfile) file as a template

3. Then add the OS name to this if block here https://github.com/openpubkey/opkssh/blob/722f7bc10d64ea3db2a4f586a871b5228145e48f/test/integration/ssh_server/ssh_server.go#L49-L57

4. Add SUSE to the supported linux flavors in the README https://github.com/openpubkey/opkssh?tab=readme-ov-file#server-support
author_association: MEMBER
user: login: SweBarre
created_at: 2025-05-16T21:28:58Z
body: I don't know if I can do that, but I would love to give it a try.

author_association: CONTRIBUTOR
user: login: SweBarre
created_at: 2025-05-17T16:28:02Z
body: @EthanHeilman, 
I'm not really sure on how to test my Dockerfile correctly before I push the changes.
I've used the `./hack/integration-tests.sh` and changed the `OS_TYPE` to opensuse, it seems to use the correct Dockerfile but I keep getting this error
```
 add_test.go:51: Command `/bin/bash -c /usr/local/bin/opkssh add test foo@example.com http://oidc.local:9998/` being run as user test2 returned exit code 1 and the following stdout/stderr:
     Failed to add to policy: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
     Error: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
```
I've been trying to understand the code in add_test.go but fails miserably due to lack of go experience,
any pointers on what that could be causing that error? 

author_association: CONTRIBUTOR
user: login: EthanHeilman
created_at: 2025-05-17T22:12:45Z
body: You are running as the user `test2` but attempting to edit a file in `/home/test`, test2 does not have permissions to get files in test's home directory.

What's the full error message.
author_association: MEMBER
user: login: SweBarre
created_at: 2025-05-18T11:07:25Z
body: It's the following lines
```
2025/05/17 18:12:36 🔔 Container is ready: b217df0a71f4
    add_test.go:51: Command `test -f /home/test2/.opk/auth_id` being run as user root returned exit code 1 and the following stdout/stderr:
    add_test.go:51: Command `mkdir -p /home/test2/.opk` being run as user root returned exit code 0 and the following stdout/stderr:
    add_test.go:51: Command `touch /home/test2/.opk/auth_id` being run as user root returned exit code 0 and the following stdout/stderr:
    add_test.go:51: Command `chmod 600 /home/test2/.opk/auth_id` being run as user root returned exit code 0 and the following stdout/stderr:
    add_test.go:51: Command `chown test2:test2 /home/test2/.opk/auth_id` being run as user root returned exit code 0 and the following stdout/stderr:
    add_test.go:51: Command `test -f /home/test2/.opk/auth_id` being run as user root returned exit code 0 and the following stdout/stderr:
    add_test.go:51: Command `/bin/bash -c /usr/local/bin/opkssh add test foo@example.com http://oidc.local:9998/` being run as user test2 returned exit code 1 and the following stdout/stderr:
        Failed to add to policy: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
        Error: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
    add_test.go:51: Command `cat /home/test2/.opk/auth_id` being run as user root returned exit code 0 and the following stdout/stderr:
    add_test.go:51: Command `cat /etc/opk/auth_id` being run as user root returned exit code 0 and the following stdout/stderr:

```
I just tested with the ubuntu image and see the same
```
       Failed to add to policy: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
        Error: failed to create policy file: stat /home/test/.opk/auth_id: permission denied

```

author_association: CONTRIBUTOR
user: login: EthanHeilman
created_at: 2025-05-18T14:28:49Z
body: This isn't a test failure it is a test passing, but the output message could be more helpful

It is this test checking that the user test2 can not add users to the user test's auth_id file.
https://github.com/openpubkey/opkssh/blob/93e52ef0a47eb98025a46e88ecd9ce84b5f88837/test/integration/add_test.go#L135-L143

The test passes if the action fails.

```
025-05-15T18:10:43.1213256Z     add_test.go:51: Command `/bin/bash -c /usr/local/bin/opkssh add test foo@example.com http://oidc.local:9998/` being run as user test2 returned exit code 1 and the following stdout/stderr:
2025-05-15T18:10:43.1215282Z         Failed to add to policy: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
2025-05-15T18:10:43.1216452Z         Error: failed to create policy file: stat /home/test/.opk/auth_id: permission denied
...
2025-05-15T18:10:43.3141017Z --- PASS: TestAdd (100.99s)
2025-05-15T18:10:43.3141697Z     --- PASS: TestAdd/sudoer_user_can_update_root_policy (98.26s)
2025-05-15T18:10:43.3142642Z     --- PASS: TestAdd/sudoer_user_can_update_root_policy_with_principal_!=_self (0.54s)
2025-05-15T18:10:43.3143863Z     --- PASS: TestAdd/unprivileged_user_creates_an_auth_id_file_(no_preexisting_~/.opk/auth_id_file) (0.61s)
2025-05-15T18:10:43.3144950Z     --- PASS: TestAdd/unprivileged_user_can_update_their_user_policy (0.81s)
2025-05-15T18:10:43.3145865Z     --- PASS: TestAdd/unprivileged_user_cannot_add_principal_!=_self (0.77s)
```

https://productionresultssa1.blob.core.windows.net/actions-results/de755d05-6a59-4d88-b286-f1a692d034e8/workflow-job-run-ee410287-9ceb-57df-885d-0974dfec421d/logs/job/job-logs.txt?rsct=text%2Fplain&se=2025-05-18T14%3A33%3A24Z&sig=EogK9pXX6z0ddZFcUHVYQRysLH4mSBBhGpYqM1Txmno%3D&ske=2025-05-19T01%3A59%3A38Z&skoid=ca7593d4-ee42-46cd-af88-8b886a2f84eb&sks=b&skt=2025-05-18T13%3A59%3A38Z&sktid=398a6654-997b-47e9-b12b-9515b896b4de&skv=2025-05-05&sp=r&spr=https&sr=b&st=2025-05-18T14%3A23%3A19Z&sv=2025-05-05
author_association: MEMBER
user: login: SweBarre
created_at: 2025-05-19T06:03:45Z
body: Got it, thanks.
I'll push the changes that you suggested later today.
Thanks for all your help and patience.
author_association: CONTRIBUTOR
user: login: SweBarre
created_at: 2025-05-19T06:54:27Z
body: Looks like the CI tests for openSUSE was successfully executed \o/
author_association: CONTRIBUTOR
user: login: EthanHeilman
created_at: 2025-05-19T13:02:27Z
body: @SweBarre Great work! Is this ready for review?
author_association: MEMBER
user: login: SweBarre
created_at: 2025-05-19T17:03:02Z
body: Yes, 
I think it's ready
author_association: CONTRIBUTOR