File: install-linux.sh

package info (click to toggle)
opkssh 0.8.0-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,428 kB
  • sloc: sh: 1,981; makefile: 4
file content (783 lines) | stat: -rw-r--r-- 27,157 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
#!/usr/bin/env bash
# ==============================================================================
# Usage: install-linux.sh [OPTIONS]
#
# Options:
#   --no-home-policy
#       Disables configuration that allows opkssh to see policy files in user's
#       home directory (/home/<username>/auth_id). Greatly simplifies install.
#
#   --no-sshd-restart
#       Do not restart SSH after installation.
#
#   --overwrite-config
#       Overwrite the currently active sshd configuration for
#       AuthorizedKeysCommand and AuthorizedKeysCommandUser directives.
#
#   --install-from=FILEPATH
#       Install using a local file instead of downloading from GitHub.
#
#   --install-version=VERSION
#       Install a specific version from GitHub instead of "latest".
#
#   --help
#       Display this help message.
# ==============================================================================
#

if [[ "$SHUNIT_RUNNING" != "1" ]]; then
    # Exit if any command fails, unless running tests
    set -e
fi

# Setting global variables

# OPKSSH_AUTH_CMD_USER
# Default: opksshuser
# Description: The system user responsible for executing the AuthorizedKeysCommand
AUTH_CMD_USER="${OPKSSH_INSTALL_AUTH_CMD_USER:-opksshuser}"

# OPKSSH_AUTH_CMD_GROUP
# Default: opksshuser
# Description: Group ownership for installed files and directories
AUTH_CMD_GROUP="${OPKSSH_INSTALL_AUTH_CMD_GROUP:-opksshuser}"

# OPKSSH_SUDOERS_PATH
# Default: /etc/sudoers.d/opkssh
# Description: Path to the sudoers file for opkssh
SUDOERS_PATH="${OPKSSH_INSTALL_SUDOERS_PATH:-/etc/sudoers.d/opkssh}"

# OPKSSH_HOME_POLICY
# Default: true
# Description: Whether to use the home directory policy feature
HOME_POLICY="${OPKSSH_INSTALL_HOME_POLICY:-true}"

# OPKSSH_RESTART_SSH
# Default: true
# Description: Whether to restart SSH after installation
RESTART_SSH="${OPKSSH_INSTALL_RESTART_SSH:-true}"

# OPKSSH_OVERWRITE_ACTIVE_CONFIG
# Default: false
# Description: Overwrite any existing active opkssh config
OVERWRITE_ACTIVE_CONFIG="${OPKSSH_INSTALL_OVERWRITE_ACTIVE_CONFIG:-false}"

# OPKSSH_LOCAL_INSTALL_FILE
# Default: (empty)
# Description: Path to local install file, used instead of downloading from GitHub
LOCAL_INSTALL_FILE="${OPKSSH_INSTALL_LOCAL_INSTALL_FILE:-}"

# OPKSSH_INSTALL_VERSION
# Default: latest
# Description: Which version of opkssh to install from GitHub
INSTALL_VERSION="${OPKSSH_INSTALL_VERSION:-latest}"

# OPKSSH_INSTALL_DIR
# Default: /usr/local/bin
# Description: Where to install the opkssh binary
INSTALL_DIR="${OPKSSH_INSTALL_DIR:-/usr/local/bin}"

# OPKSSH_BINARY_NAME
# Default: opkssh
# Description: Name of the installed binary
BINARY_NAME="${OPKSSH_INSTALL_BINARY_NAME:-opkssh}"

# OPKSSH_GITHUB_REPO
# Default: openpubkey/opkssh
# Description: GitHub repository to download the opkssh binary from
GITHUB_REPO="${OPKSSH_INSTALL_GITHUB_REPO:-openpubkey/opkssh}"

# Global variables used by several functions
OS_TYPE=""
CPU_ARCH=""

# file_exists
# check is file exists, helpers that wrap real commands so it can be
# overridden in tests
#
# Arguments:
#   $1 - Path to file
#
# Returns:
#  0 if the file exists, otherwise
file_exists() { [[ -f "$1" ]]; }

# dir_exists
# check is directory exists, helpers that wrap real commands so it can be
# overridden in tests
#
# Arguments:
#   $1 - Path to directory
#
# Returns:
#  0 if the directory exists, otherwise
dir_exists() { [[ -d "$1" ]]; }

# check_bash_version
# Checks if a bash version is >= 3.2
#
# Arguments:
#   $1 - Major version
#   $2 - Minor version
#   $3 - Patch lever (optional, not used)
#   $4 - Build version (optional, not used)
#   $5 - Version string (optional, not used)
#   $6 - Vendor (optional, not used)
#   $7 - Operating system (optional, not used)
#
# Returns:
#   0 if version >= 3.2, 1 otherwise
#
# Example:
#   check_bash_version "${BASH_VERSINFO[@]}"
check_bash_version() {
    local major=$1
    local minor=$2

    if ((major > 3)); then
        echo "Bash version: $major.$minor"
        return 0
    elif ((major == 3 && minor >= 2)); then
        echo "Bash version: $major.$minor"
        return 0
    else
        echo "Error: Unsupported Bash version: $major.$minor" >&2
        return 1
    fi
}

# determine_linux_type
# Determine the linux type the script is executed in
#
# Outputs:
#   Writes the current Linux type detected
#
# Returns:
#   0 if successful, 1 if it's an unsupported OS
determine_linux_type() {
    local os_type
    if file_exists "/etc/redhat-release" ; then
        os_type="redhat"
    elif file_exists "/etc/debian_version" ; then
        os_type="debian"
    elif file_exists "/etc/arch-release"; then
        os_type="arch"
    elif file_exists "/etc/os-release" && \
        grep -q '^ID_LIKE=.*suse' /etc/os-release; then
        os_type="suse"
    else
        echo "Unsupported OS type."
        return 1
    fi
    echo "$os_type"
}

# check_cpu_architecture
# Checks the CPU architecture the script is running on
#
# Outputs:
#   Writes the CPU architechture the script is runnin on
#
# Returns:
#   0 if running on supported architectur, 1 otherwise
check_cpu_architecture() {
    local cpu_arch
    cpu_arch="$(uname -m)"
    case "$cpu_arch" in
        x86_64)
            cpu_arch="amd64"
            ;;
        aarch64)
            cpu_arch="arm64"
            ;;
        amd64 | arm64)
            # Supported architectures, no changes needed
            ;;
        *)
            echo "Error: Unsupported CPU architecture: $cpu_arch." >&2
            return 1
            ;;
    esac
    echo "$cpu_arch"
}

# running_as_root
# Checks if the script executes as root
#
# Arguments:
#   $1 - UID of user to check
#
# Returns:
#   0 if running as root, 1 otherwise
running_as_root() {
    userid="$1"
    if [[ "$userid" -ne 0 ]]; then
        echo "Error: This script must be run as root." >&2
        echo "sudo $0" >&2
        return 1
    fi
}

# display_help_message
# Prints script help message to stdout
#
# Returns:
#   0 on success
display_help_message() {
    echo "Usage: $0 [OPTIONS]"
    echo ""
    echo "Options:"
    echo "  --no-home-policy         Disables configuration that allows opkssh see policy files in user's home directory"
    echo "                           (/home/<username>/auth_id). Greatly simplifies install, try this if you are having install failures."
    echo "  --no-sshd-restart        Do not restart SSH after installation"
    echo "  --overwrite-config       Overwrite the currently active sshd configuration for AuthorizedKeysCommand and AuthorizedKeysCommandUser"
    echo "                           directives. This may be necessary if the script cannot create a configuration with higher priority in /etc/ssh/sshd_config.d/."
    echo "  --install-from=FILEPATH  Install using a local file"
    echo "  --install-version=VER    Install a specific version from GitHub"
    echo "  --help                   Display this help message"
}

# ensure_command
# Checks whether a given command is available on the system.
#
# Arguments:
#   $1 - Name of the command to check (e.g. "curl", "git", "netstat").
#   $2 - Name of the package the command is delivered in (e.g. "curl", "git", "net-tools-deprecated" (optional, defauls to $1)
#   $3 - OS Type the script is running on, output from function determine_linux_type (optional, default so OS_TYPE)
#
# Outputs:
#   Writes an error message to stderr if the command is missing and how to install the command on supported OS types.
#
# Returns:
#   0 if the command is found, 1 otherwise.
#
# Example:
#   ensure_command "wget" || exit 1
#   ensure_command "netstat" "net-tools-deprecated" || exit
ensure_command() {
    local cmd="$1"
    local package="${2:-$cmd}"
    local os_type="${3:-$OS_TYPE}"
    if ! command -v "$cmd" >/dev/null 2>&1; then
        echo "Error: $cmd is not installed. Please install it first." >&2
        if [[ "$os_type" == "debian" ]]; then
            echo "sudo apt install $package" >&2
        elif [[ "$os_type" == "redhat" ]]; then
            # dnf might not be available on older versions
            if command -v dnf >/dev/null 2>&1; then
                echo "sudo dnf install $package" >&2
            else
                echo "sudo yum install $package" >&2
            fi
        elif [[ "$os_type" == "arch" ]]; then
            echo "sudo pacman -S $package" >&2
        elif [[ "$os_type" == "suse" ]]; then
            echo "sudo zypper install $package" >&2
        else
            echo "Unsupported OS type." >&2
        fi
        return 1
    fi
}

# ensure_openssh_server
# Ensures that openSSH-Server is installed and configuration targets exists
#
# Arguments:
#   $1 - OS Type the script is running on, output from function determine_linux_type
#
# Outputs:
#   Writes error if openSSH isn't installed with package manager
#   Writes error if it could verify target configuration files for opkssh
#
# Returns:
#   0 if openSSH is installed with package manager and configuration files exists, 1 otherwise.
ensure_openssh_server() {
    local os_type="$1"
    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
                return 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
                return 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
                return 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
                return 1
            fi
            ;;
    esac
    # Ensure OpenSSH server configuration targets exists
    if ! file_exists /etc/ssh/sshd_config && ! dir_exists /etc/ssh/sshd_config.d; then
        echo "Neither /etc/ssh/sshd_config nor /etc/ssh/sshd_config.d exists." >&2
        return 1
    fi
}

# ensure_opkssh_user_and_group
# Checks if the group and user used bu AuthorizedKeysCommand exists if not creates it
#
# Arguments:
#   $1 - AuthorizedKeysCommand User
#   $2 - AuthorizedKeysCommand Group
#
# Outputs:
#   Writes to stdout if group created and if user is created
#
# Returns:
#   0 on success
ensure_opkssh_user_and_group() {
    local auth_cmd_user="$1"
    local auth_cmd_group="$2"
    # Checks if the group used by the AuthorizedKeysCommand exists if not creates it
    if ! getent group "$auth_cmd_group" >/dev/null; then
        groupadd --system "$auth_cmd_group"
        echo "Created group: $auth_cmd_group"
    fi
    # If the AuthorizedKeysCommand user does not exist, create it and add it to the group
    if ! getent passwd "$auth_cmd_user" >/dev/null; then
        useradd -r -M -s /sbin/nologin -g "$auth_cmd_group" "$auth_cmd_user"
        echo "Created user: $auth_cmd_user with group: $auth_cmd_group"
    else
        # If the AuthorizedKeysCommand user exist, ensure it is added to the group
        usermod -aG "$auth_cmd_group" "$auth_cmd_user"
        echo "Added $auth_cmd_user to group: $auth_cmd_group"
    fi
}

# parse_args
# Parses CLI arguments and sets configuration flags.
#
# Arguments:
#   $@ - Command-line arguments
#
# Outputs:
#   Sets global variables: HOME_POLICY, RESTART_SSH, OVERWRITE_ACTIVE_CONFIG,LOCAL_INSTALL_FILE, INSTALL_VERSION.
#
# Returns:
#   0 on success, 1 if help is in arguments
parse_args() {
    for arg in "$@"; do
        if [[ "$arg" == "--no-home-policy" ]]; then
            HOME_POLICY=false
        elif [[ "$arg" == "--help" ]]; then
            display_help_message
            return 1
        elif [[ "$arg" == "--no-sshd-restart" ]]; then
            RESTART_SSH=false
        elif [[ "$arg" == "--overwrite-config" ]]; then
            OVERWRITE_ACTIVE_CONFIG=true
        elif [[ "$arg" == --install-from=* ]]; then
            LOCAL_INSTALL_FILE="${arg#*=}"
        elif [[ "$arg" == --install-version=* ]]; then
            INSTALL_VERSION="${arg#*=}"
        fi
    done
}

# install_opkssh_binary
# Installs opkssh binary either from local file or downloads from repository
#
# Outputs:
#   Writes to stdout if installing from local file or repository or the URL from wher it's downloaded
#   Writes to stderr if install path doesn't exist
#
# Returns:
#   0 if installation is succeeded, 1 otherwise
install_opkssh_binary() {
    # Check if we should install from a local file
    if [[ -n "$LOCAL_INSTALL_FILE" ]]; then
        echo "LOCAL_INSTALL_FILE is set, installing from local file: $LOCAL_INSTALL_FILE"
        BINARY_PATH=$LOCAL_INSTALL_FILE
        if [[ ! -f "$BINARY_PATH" ]]; then
            echo "Error: Specified binary path does not exist." >&2
            return 1
        fi
        echo "Using binary from specified path: $BINARY_PATH"
    else
        if [[ "$INSTALL_VERSION" == "latest" ]]; then
            BINARY_URL="https://github.com/$GITHUB_REPO/releases/latest/download/opkssh-linux-$CPU_ARCH"
        else
            BINARY_URL="https://github.com/$GITHUB_REPO/releases/download/$INSTALL_VERSION/opkssh-linux-$CPU_ARCH"
        fi

        # Download the binary
        echo "Downloading version $INSTALL_VERSION of $BINARY_NAME from $BINARY_URL..."
        wget -q --show-progress -O "$BINARY_NAME" "$BINARY_URL"

        BINARY_PATH="$BINARY_NAME"
    fi

    # Move to installation directory
    mv "$BINARY_PATH" "$INSTALL_DIR/$BINARY_NAME"

    # Make the binary executable, correct permissions/ownership
    chmod +x "$INSTALL_DIR/$BINARY_NAME"
    chown root:"${AUTH_CMD_GROUP}" "$INSTALL_DIR/$BINARY_NAME"
    chmod 755 "$INSTALL_DIR/$BINARY_NAME"

    if command -v "$INSTALL_DIR"/"$BINARY_NAME" &>/dev/null; then
        echo "Installed $BINARY_NAME to $INSTALL_DIR/$BINARY_NAME"
    else
        echo "Installation failed." >&2
        return 1
    fi
}

# check_selinux
#   Checks if SELinux is enabled and if so, ensures the context is set correctly
#
# Outputs:
#   Progress of SELinux context installation/configuration or message that SELinux is disabled
#
# Returns:
#   0 if SELinux is disabled or if context is correctly
check_selinux() {
    if command -v getenforce >/dev/null 2>&1; then
        if [[ "$(getenforce)" != "Disabled" ]]; then
            echo "SELinux detected. Configuring SELinux for opkssh"
            echo "  Restoring context for $INSTALL_DIR/$BINARY_NAME..."
            restorecon "$INSTALL_DIR/$BINARY_NAME"

        # Create temporary files for the compiled module and package
            TE_TMP="/tmp/opkssh.te"
            MOD_TMP="/tmp/opkssh.mod" # SELinux requires that modules have the same file name as the module name
            PP_TMP="/tmp/opkssh.pp"

            if [[ "$HOME_POLICY" == true ]]; then
                echo "  Using SELinux module that permits home policy"

                # Pipe the TE directives into checkmodule via /dev/stdin
                cat <<'EOF' >"$TE_TMP"
module opkssh 1.0;


require {
        type sshd_t;
        type var_log_t;
        type http_port_t;
        type sudo_exec_t;
        class file { append execute execute_no_trans open read map };
        class tcp_socket name_connect;
}


# We need to allow the AuthorizedKeysCommand opkssh process launched by sshd to:

# 1. Make TCP connections to ports labeled http_port_t. This is so opkssh can download the public keys of the OpenID providers.
allow sshd_t http_port_t:tcp_socket name_connect;

# 2. Needed to allow opkssh to call `sudo opkssh readhome` to read the policy file in the user's home directory
allow sshd_t sudo_exec_t:file { execute execute_no_trans open read map };

# 3. Needed to allow opkssh to write to its log file
allow sshd_t var_log_t:file { open append };
EOF

            else
                echo "  Using SELinux module does not permits home policy (--no-home-policy option supplied)"
                # Redefine the tmp file names since SELinux modules must have the same name as the file
                TE_TMP="/tmp/opkssh-no-home.te"
                MOD_TMP="/tmp/opkssh-no-home.mod" # SELinux requires that modules have the same file name as the module name
                PP_TMP="/tmp/opkssh-no-home.pp"

                # Pipe the TE directives into checkmodule via /dev/stdin
                cat <<'EOF' >"$TE_TMP"
module opkssh-no-home 1.0;

require {
        type sshd_t;
        type var_log_t;
        type http_port_t;
        class file { append execute execute_no_trans open read map };
        class tcp_socket name_connect;
}


# We need to allow the AuthorizedKeysCommand opkssh process launched by sshd to:

# 1. Make TCP connections to ports labeled http_port_t. This is so opkssh can download the public keys of the OpenID providers.
allow sshd_t http_port_t:tcp_socket name_connect;

# 2. Needed to allow opkssh to write to its log file
allow sshd_t var_log_t:file { open append };
EOF
            fi

            echo "  Compiling SELinux module..."
            checkmodule -M -m -o "$MOD_TMP" "$TE_TMP"

            echo "  Packaging module..."
            semodule_package -o "$PP_TMP" -m "$MOD_TMP"

            echo "  Installing module..."
            semodule -i "$PP_TMP"

            rm -f "$TE_TMP" "$MOD_TMP" "$PP_TMP"
            echo "SELinux module installed successfully!"
        fi
    else
        echo "SELinux is disabled"
    fi
}

# configure_opkssh
# Creates/checks the opskssh configuration
#
# Arguments:
#   $1 - Path to etc directory (Optional, default /etc)
#
# Outputs:
#   Writes to stdout the configration progress
#
# Returns:
#   0
# shellcheck disable=SC2120
configure_opkssh() {
    local etc_path="${1:-/etc}"
    # Define the default OpenID Providers
    local provider_google="https://accounts.google.com 206584157355-7cbe4s640tvm7naoludob4ut1emii7sf.apps.googleusercontent.com 24h"
    local provider_microsoft="https://login.microsoftonline.com/9188040d-6c67-4c5b-b112-36a304b66dad/v2.0 096ce0a3-5e72-4da8-9c86-12924b294a01 24h"
    local provider_gitlab="https://gitlab.com 8d8b7024572c7fd501f64374dec6bba37096783dfcd792b3988104be08cb6923 24h"
    local provider_hello="https://issuer.hello.coop app_xejobTKEsDNSRd5vofKB2iay_2rN 24h"

    echo "Configuring opkssh:"

    if [[ ! -e "$etc_path/opk" ]]; then
        mkdir -p "$etc_path/opk"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk"
        chmod 750 "$etc_path/opk"
    fi

    if [[ ! -e "$etc_path/opk/policy.d" ]]; then
        mkdir -p "$etc_path/opk/policy.d"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/policy.d"
        chmod 750 "$etc_path/opk/policy.d"
    fi

    if [[ ! -e "$etc_path/opk/auth_id" ]]; then
        touch "$etc_path/opk/auth_id"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/auth_id"
        chmod 640 "$etc_path/opk/auth_id"
    fi

    if [[ ! -e "$etc_path/opk/config.yml" ]]; then
        touch "$etc_path/opk/config.yml"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/config.yml"
        chmod 640 "$etc_path/opk/config.yml"
    fi

    if [[ ! -e "$etc_path/opk/providers" ]]; then
        touch "$etc_path/opk/providers"
        chown root:"${AUTH_CMD_GROUP}" "$etc_path/opk/providers"
        chmod 640 "$etc_path/opk/providers"
    fi

    if [[ -s "$etc_path/opk/providers" ]]; then
        echo "  The providers policy file (/etc/opk/providers) is not empty. Keeping existing values"
    else
        {
            echo "$provider_google"
            echo "$provider_microsoft"
            echo "$provider_gitlab"
            echo "$provider_hello"
        } >> "$etc_path/opk/providers"
    fi
}

# configure_openssh_server
# Configure openSSH-server to use opkssh using AuthorizedKeysCommand
#
# Arguments:
#   $1 - Path to ssh root configuratino directory (Optional, default /etc/ssh)
#
# Output:
#   Writes to stdout the progress of configuration
#
# Returns:
#   0 if succeeded, otherwise 1
# shellcheck disable=SC2120
configure_openssh_server() {
    local ssh_root="${1:-/etc/ssh}"
    local sshd_config="$ssh_root/sshd_config"
    local sshd_config_d="$ssh_root/sshd_config.d"
    local auth_key_cmd="AuthorizedKeysCommand ${INSTALL_DIR}/${BINARY_NAME} verify %u %k %t"
    local auth_key_user="AuthorizedKeysCommandUser ${AUTH_CMD_USER}"
    local opk_config_suffix="opk-ssh.conf"
    local new_prefix=""
    local active_config=""

    if [[ ! -f "$sshd_config" ]] || \
        (grep -Fxq 'Include /etc/ssh/sshd_config.d/*.conf' "$sshd_config" &&
            { ! grep -Eq '^AuthorizedKeysCommand|^AuthorizedKeysCommandUser' "$sshd_config" ||
                ! grep -Eq '^AuthorizedKeysCommand|^AuthorizedKeysCommandUser' "$sshd_config_d"/*.conf 2>/dev/null; }); then
        # Configuration should be put in /etc/ssh/sshd_config.d director
        # Find active configuration file with the directives we're interested in (sorted numerically)
        active_config=$(find "$sshd_config_d"/*.conf -exec grep -l '^AuthorizedKeysCommand\|^AuthorizedKeysCommandUser' {} \; 2>/dev/null | sort -V | head -n 1)

        if [[ "$active_config" == *"$opk_config_suffix" ]] || [[ "$OVERWRITE_ACTIVE_CONFIG" == true ]]; then
            # Overwrite the configuration, either from a previous run of this script or because user request it for the currently active config
            sed -i '/^AuthorizedKeysCommand /s/^/#/' "$active_config"
            sed -i '/^AuthorizedKeysCommandUser /s/^/#/' "$active_config"
            echo "$auth_key_cmd" >> "$active_config"
            echo "$auth_key_user" >> "$active_config"
        elif [[ "$(basename "$active_config")" =~ ^0+[^0-9]+ ]]; then
            # The active config starts with all zeros and is therefore the one with the
            # highest priority. We cannot add a new file with even higher priority.
            echo "  Cannot create configuration with higher priority. Remove $active_config or rerun the script with the --overwrite-config flag to overwrite"
            return 1
        else
            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="${sshd_config_d}/${new_prefix}-$opk_config_suffix"
            echo "$auth_key_cmd" > "$new_config"
            echo "$auth_key_user" >> "$new_config"
        fi
    else
        # The directives in 'sshd_config' are active
        sed -i '/^AuthorizedKeysCommand /s/^/#/' "$sshd_config"
        sed -i '/^AuthorizedKeysCommandUser /s/^/#/' "$sshd_config"
        echo "$auth_key_cmd" >> "$sshd_config"
        echo "$auth_key_user" >> "$sshd_config"
    fi
}

# restart_openssh_server
# Checks if RESTART_SSH is true and restarts the openSSH server daemon if that is the case
#
# Outputs:
#   Writes to stdout the status if the daemon is restarted
#   Writes to stdout is set to false and skipping daemon restart
#
# Returns:
#   0 if successful, 1 if it's an unsupported OS_TYPE
restart_openssh_server() {
    if [[ "$RESTART_SSH" == true ]]; then
        if [[ "$OS_TYPE" == "debian" ]]; then
            systemctl restart ssh
        elif [[ "$OS_TYPE" == "redhat" ]] || [[ "$OS_TYPE" == "arch" ]] || [[ "$OS_TYPE" == "suse" ]]; then
            systemctl restart sshd
        else
            echo "  Unsupported OS type."
            return 1
        fi
    else
        echo "  RESTART_SSH is not true, skipping SSH restart."
    fi
}

# configure_sudo
# Configures sudo for opkssh if HOME_POLICY is set to true
#
# Outputs:
#   Writes to stdout the progress of sudo configuration if HOME_POLICY=true
#   Writes to stdout that sudo is not configured if HOME_POLICY=false
#
# Returns:
#   0
configure_sudo() {
    if [[ "$HOME_POLICY" == true ]]; then
        if [[ ! -f "$SUDOERS_PATH" ]]; then
            echo "  Creating sudoers file at $SUDOERS_PATH..."
            touch "$SUDOERS_PATH"
            chmod 440 "$SUDOERS_PATH"
        fi
        SUDOERS_RULE_READ_HOME="$AUTH_CMD_USER ALL=(ALL) NOPASSWD: ${INSTALL_DIR}/${BINARY_NAME} 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"
            echo "$SUDOERS_RULE_READ_HOME" >> "$SUDOERS_PATH"
        fi
    else
        echo "  Skipping sudoers configuration as it is only needed for home policy (HOME_POLICY is set to false)"
    fi
}



# log_opkssh_installation
# Log the installation details to /var/log/opkssh.log to help with debugging
#
# Arguments:
#   $1 - Path to opkssh log file (Optional, default /var/log/opkssh.log)
#
# Output:
#   Writes to stdout that installation is successful
#   Writes installation debug information to /var/log/opkssh.log
#
# Returns:
#   0
# shellcheck disable=SC2120
log_opkssh_installation() {
    local log_file="${1:-/var/log/opkssh.log}"
    touch "$log_file"
    chown root:"${AUTH_CMD_GROUP}" "$log_file"
    chmod 660 "$log_file"

    VERSION_INSTALLED=$("$INSTALL_DIR"/"$BINARY_NAME" --version)
    INSTALLED_ON=$(date)
    # Log the installation details to /var/log/opkssh.log to help with debugging
    echo "Successfully installed opkssh (INSTALLED_ON: $INSTALLED_ON, VERSION_INSTALLED: $VERSION_INSTALLED, INSTALL_VERSION: $INSTALL_VERSION, LOCAL_INSTALL_FILE: $LOCAL_INSTALL_FILE, HOME_POLICY: $HOME_POLICY, RESTART_SSH: $RESTART_SSH)" >> "$log_file"

    echo "Installation successful! Run '$BINARY_NAME' to use it."
}

# main
# Running main function only if executed, not sourced
#
# Arguments:
#   "$@"
#
# Returns:
#   0 if opkssh installs successfully, 1 if installation failed
main() {
    parse_args "$@" || return 0
    check_bash_version "${BASH_VERSINFO[@]}" || return 1
    running_as_root "$EUID" || return 1
    OS_TYPE=$(determine_linux_type) || return 1
    CPU_ARCH=$(check_cpu_architecture) || return 1
    ensure_command "wget" || return 1
    if [[ "$HOME_POLICY" == true ]]; then
        ensure_command "sudo" || return 1
    fi
    ensure_opkssh_user_and_group "$AUTH_CMD_USER" "$AUTH_CMD_GROUP" || return 1
    ensure_openssh_server "$OS_TYPE" || return 1
    install_opkssh_binary || return 1
    check_selinux
    configure_opkssh
    configure_openssh_server || return 1
    restart_openssh_server || return 1
    if [[ "$HOME_POLICY" == true ]]; then
        configure_sudo
    fi
    log_opkssh_installation
}

# Don't run main during testing (SH unit tests source this script)
if [[ -z "$SHUNIT_RUNNING" ]]; then
    main "$@"
    exit $?
fi