Browse Source

[enh] Config panel

ljf 3 years ago
parent
commit
2c46a2b48e
2 changed files with 495 additions and 0 deletions
  1. 139 0
      config_panel.toml
  2. 356 0
      scripts/config

+ 139 - 0
config_panel.toml

@@ -0,0 +1,139 @@
+version = "0.1"
+name = "VPN Client configuration panel"
+
+[main]
+name = "Auto-configuration"
+
+    [main.cube]
+    name = ""
+        
+        [main.cube.warning]
+        ask = "To configure your vpn client, the use of a VPN supporting the .cube file is recommended."
+        type = "warning"
+        
+        [main.cube.service_enabled]
+        ask = "Enable VPN"
+        type = "boolean"
+        choices = ["on", "off"]
+        default = "off"
+        help = "If you select 'off' your VPN will not run even if you restart your server"
+
+        [main.cube.cube_file]
+        ask = "Load a cube File"
+        type = "file"
+        optional = true
+        default = ""
+        helpLink = "<a href='' target='_BLANK'>See the list of .cube file providers</a><br><a href='https://labriqueinter.net/dotcubefiles.html' target='_BLANK'>Info about cube file format</a>"
+        
+[manual]
+name = "Manual configuration"
+
+    [manual.vpn]
+    name = "VPN"
+        
+        [manual.vpn.warning]
+        ask = "If you have not a .cube file you can configure your VPN manually.<br/>IMPORTANT: this app only supports fixed and dedicated public ip VPNs."
+        helpLink = "<a href='' target='_BLANK'>See the list of public ip vpn providers</a>"
+        type = "warning"
+
+        
+        [manual.vpn.server_name]
+        ask = "Server address"
+        type = "string"
+        default = ""
+        optional = true
+        pattern = "^[^/ ]*$"
+        help = "Prefer to put directly the IP address of your VPN server here instead of domain name"
+        
+        [manual.vpn.server_port]
+        ask = "Server Port"
+        type = "number"
+        default = "1194"
+        min = "1"
+        max = "65535"
+        help = "Default openvpn port is 1194. If your server is connected to a restricted network and if your VPN provider support it, set 443 should work better."
+        
+        [manual.vpn.server_proto]
+        ask = "Protocol"
+        choices = ["udp", "tcp"]
+        default = "udp"
+        help = "UDP is preferred for performance reason. If your server is connected to a restricted network, and UDP does not work, try with TCP."
+        
+        [manual.vpn.server_ip6]
+        ask = "Delegated prefix (IPv6)"
+        type = "string"
+        default = ""
+        optional = true
+        pattern = "^[0-9a-fA-F:]+$"
+
+    [manual.auth]
+    name = "Authentication"
+        
+        [manual.auth.server_ca]
+        ask = "Update Server CA"
+        type = "file"
+        default = ""
+        optional = true
+        help = "You should upload a CA certificate to start"
+        source="/etc/openvpn/keys/ca-server.crt"
+        
+        [manual.auth.crt]
+        ask = "Update Client Certificate"
+        type = "file"
+        default = ""
+        source="/etc/openvpn/keys/user.crt"
+        
+        [manual.auth.key]
+        ask = "Update Client Key"
+        type = "file"
+        default = ""
+        help = "This file begins with -----BEGIN PRIVATE KEY-----"
+        optional = true
+        example = "-----BEGIN PRIVATE KEY-----"
+        source="/etc/openvpn/keys/user.key"
+        
+        [manual.auth.ta]
+        ask = "Upload Shared-Secret"
+        type = "file"
+        default = ""
+        optional = true
+        source="/etc/openvpn/keys/user_ta.key"
+        
+        [manual.auth.login_user]
+        ask = "Username"
+        type = "string"
+        default = ""
+        optional = true
+        pattern = "^[^/ ]+$"
+        
+        [manual.auth.login_passphrase]
+        ask = "Password"
+        type = "password"
+        default = ""
+        optional = true
+
+    [manual.dns]
+    name = "DNS"
+
+        [manual.dns.dns0]
+        ask = "First resolver"
+        type = "string"
+        default = ""
+        optional = true
+        pattern = "^([0-9.]{8,16}|[0-9a-fA-F:]+)$"
+
+        [manual.dns.dns1]
+        ask = "Second resolver"
+        type = "string"
+        default = ""
+        optional = true
+        pattern = "^([0-9.]{8,16}|[0-9a-fA-F:]+)$"
+
+    [manual.advanced]
+    name = "Advanced configuration"
+
+        [manual.advanced.ovpn]
+        ask = "OVPN template"
+        type = "text"
+        optional = true
+        source="/etc/openvpn/client.conf.tpl"

+ 356 - 0
scripts/config

@@ -0,0 +1,356 @@
+#!/bin/bash
+
+#=================================================
+# GENERIC STARTING
+#=================================================
+# IMPORT GENERIC HELPERS
+#=================================================
+
+source _common.sh
+source /usr/share/yunohost/helpers
+
+#=================================================
+# RETRIEVE ARGUMENTS
+#=================================================
+
+app=$YNH_APP_INSTANCE_NAME
+
+final_path=$(ynh_app_setting_get $app final_path)
+#=================================================
+# SPECIFIC CODE
+#=================================================
+# DECLARE GENERIC FUNCTION
+#=================================================
+
+lowerdot_to_uppersnake() {
+    local lowerdot
+    lowerdot=$(echo "$1" | cut -d= -f1 | sed "s/\./_/g")
+    echo "${lowerdot^^}"
+}
+
+_ynh_panel_get() {
+    
+    # From settings
+    local params_sources
+    params_sources=`python << EOL
+import toml
+from collections import OrderedDict
+with open("/etc/yunohost/apps/vpnclient/config_panel.toml", "r") as f:
+    file_content = f.read()
+loaded_toml = toml.loads(file_content, _dict=OrderedDict)
+
+for panel_name,panel in loaded_toml.items():
+    if isinstance(panel, dict):
+        for section_name, section in panel.items():
+            if isinstance(section, dict):
+                for name, param in section.items():
+                    if isinstance(param, dict) and param.get('source', '') == 'settings':
+                        print("%s.%s.%s=%s" %(panel_name, section_name, name, param.get('source', 'settings')))
+EOL
+`
+    for param_source in params_sources
+    do
+        local _dot_setting=$(echo "$param_source" | cut -d= -f1)
+        local _snake_setting="YNH_CONFIG_$(lowerdot_to_uppersnake $dot_setting)"
+        local short_setting=$(echo "$_dot_setting" | cut -d. -f3)
+        local _getter="get__${short_setting}"
+        local source="$(echo $param_source | cut -d= -f2)"
+
+        # Get value from getter if exists
+        if type $getter | grep -q '^function$' 2>/dev/null; then
+            old[$short_setting]="$($getter)"
+
+        # 
+        elif [[ "$source" != "settings" ]]
+        then
+            old[$short_setting]="$source"
+        
+        # By default, get value from settings.yml
+        else
+            old[$short_setting]="$(ynh_app_setting_get $app $short_setting)"
+        fi
+
+    done
+    
+    
+}
+
+_ynh_panel_apply() {
+    for short_setting in "${!dot_settings[@]}"
+    do
+        local setter="set__${short_setting}"
+        local source="$sources[$short_setting]"
+        
+        # Apply setter if exists
+        if type $setter | grep -q '^function$' 2>/dev/null; then
+            $setter
+
+        # Copy file in right place 
+        elif [[ "$source" != "settings" ]]
+        then
+            cp "$new[$short_setting]" "$source"
+
+        # By default, set value into settings.yml
+        else
+            ynh_app_setting_get $app $short_setting "$new[$short_setting]"
+        fi
+    done
+}
+
+_ynh_panel_show() {
+    for short_setting in "${!old[@]}"
+    do
+        local key="YNH_CONFIG_$(lowerdot_to_uppersnake $dot_settings[$short_setting])"
+	    ynh_return "$key=${old[$short_setting]}"
+    done
+}
+
+_ynh_panel_validate() {
+    # Change detection
+    local is_error=true
+    #for changed_status in "${!changed[@]}"
+    for short_setting in "${!dot_settings[@]}"
+    do
+        #TODO file hash
+                file_hash[$setting]=$(sha256sum "$_source" | cut -d' ' -f1)
+                file_hash[$form_setting]=$(sha256sum "${!form_setting}" | cut -d' ' -f1)
+                if [[ "${file_hash[$setting]}" != "${file_hash[$form_setting]}" ]]
+                then
+                    changed[$setting]=true
+                fi
+        if [[ "$new[$short_setting]" == "$old[$short_setting]" ]]
+        then
+            changed[$short_setting]=false
+        else
+            changed[$short_setting]=true
+            is_error=false
+        fi
+    done
+    
+    # Run validation if something is changed
+    if [[ "$is_error" == "false" ]]
+    then
+
+        for short_setting in "${!dot_settings[@]}"
+        do
+            local result="$(validate__$short_setting)"
+            local key="YNH_ERROR_$(lowerdot_to_uppersnake $dot_settings[$short_setting])"
+            if [ -n "$result" ]
+            then
+                ynh_return "$key=$result"
+                is_error=true
+            fi
+        done
+    fi
+
+    if [[ "$is_error" == "true" ]]
+    then
+        ynh_die
+    fi 
+    
+}
+
+ynh_panel_init() {
+    declare -A old=()
+    declare -A changed=()
+    declare -A file_hash=()
+    
+    ynh_panel_get
+}
+
+ynh_panel_show() {
+    _ynh_panel_show
+}
+
+ynh_panel_validate() {
+    _ynh_panel_validate
+}
+
+ynh_panel_apply() {
+    _ynh_panel_apply
+}
+
+#=================================================
+# SPECIFIC GETTERS FOR TOML SHORT KEY
+#=================================================
+
+get__status() {
+    if [ -f "/sys/class/net/tun0/operstate" ] && [ "$(cat /sys/class/net/tun0/operstate)" == "up" ]
+    then
+        echo "running"
+    else
+        echo "not running"
+    fi
+}
+
+get__login_user() {
+    if [ -s /etc/openvpn/keys/credentials ]
+    then
+        sed -n 1p /etc/openvpn/keys/credentials 
+    fi
+}
+
+get__login_passphrase() {
+    if [ -s /etc/openvpn/keys/credentials ]
+    then
+        sed -n 2p /etc/openvpn/keys/credentials 
+    fi
+}
+
+
+#=================================================
+# SPECIFIC VALIDATORS FOR TOML SHORT KEYS
+#=================================================
+validate__login_user() {
+    [[ -n "$login_passphrase" && -z "$login_user" ]] &&
+        echo 'A Username is needed when you suggest a Password'
+}
+
+validate__login_passphrase() {
+    [[ -n "$login_user" && -z "$login_passphrase" ]] &&
+        echo 'A Password is needed when you suggest a Username'
+}
+
+validate__crt() {
+    [[ -n "$key" && -z "$crt" ]] &&
+        echo "A Client Certificate is needed when you suggest a Key"
+}
+
+validate__key() {
+    [[ -n "$crt" && -z "$key" ]] &&
+        echo "A Key is needed when you suggest a Client Certificate"
+}
+
+# TODO
+validate__server_ip6() {
+    $ipv6_expanded=$(ipv6_expanded "$server_ip6")
+    if [[ -z "$ipv6_expanded" ]]
+    then 
+        echo 'The IPv6 Delegated Prefix format looks bad'
+    fi
+
+    #        $ip6_blocs = explode(':', $ip6_net);
+    #    $ip6_addr = "${ip6_blocs[0]}:${ip6_blocs[1]}:${ip6_blocs[2]}:${ip6_blocs[3]}:${ip6_blocs[4]}:${ip6_blocs[5]}:${ip6_blocs[6]}:42";
+
+    #    $ip6_net = ipv6_compressed($ip6_net);
+    #    $ip6_addr = ipv6_compressed($ip6_addr);
+}
+
+#=================================================
+# SPECIFIC SETTERS FOR TOML SHORT KEYS
+#=================================================
+set__login_user() {
+    if [ -z "$login_user" ]
+    then
+        echo "$login_user\n$login_passphrase" > /etc/openvpn/keys/credentials 
+    else
+        echo "" > /etc/openvpn/keys/credentials
+    fi
+}
+
+set__login_passphrase() {
+    :
+}
+
+# TODO
+set__cube_file() {
+    if [ -f "$cube_file" ]
+    then
+        cp -f $tmp_dir/client.conf.tpl /etc/openvpn/client.conf.tpl
+    fi
+}
+
+#=================================================
+# OVERWRITING VALIDATE STEP 
+#=================================================
+ynh_panel_validate() {
+    
+    # Overwrite form response with cube files data before validation process
+    if [[ "$cube_file" ]]
+    then
+        # TODO
+        declare -A settings
+        settings[server_name]=""
+        settings[server_port]=""
+        settings[server_proto]=""
+        settings[login_user]=""
+        settings[login_passphrase]=""
+        settings[dns0]=""
+        settings[dns1]=""
+        settings[crt_server_ca]="file"
+        settings[crt_client]="file"
+        settings[crt_client_key]="file"
+        settings[crt_client_ta]="file"
+
+        tmp_dir=$(dirname "$cube_file")
+        for setting_name in "${!settings[@]}"
+        do
+            setting_value="$(jq --raw-output '.$setting_name' '$cube_file')"
+            if [[ "$setting_value" == "null" ]]
+            then
+                setting_value=''
+            # Save file in tmp dir
+            elif [[ "${settings[$setting_name]}" == "file" ]]
+            then
+                echo "${settings[$setting_name]}" | sed s/|/\n/g > $tmp_dir/$setting_name
+                setting_value="$tmp_dir/$setting_name"
+            fi
+
+            # Change settings value and changed status if needed
+            if [[ "$setting_value" != "${!setting}" ]]
+            then
+                declare "$setting='$setting_value'"
+                changed[$setting]=false
+                if [[ "$setting_value" != "${old[$setting]}" ]]
+                then
+                    changed[$setting]=true
+                fi
+            fi
+        done
+        
+        # Build specific OVPN template
+        cp -f /etc/openvpn/client.conf.tpl.restore $tmp_dir/client.conf.tpl
+        # Remove some lines
+        for rm_regex in "$(jq --raw-output '.openvpn_rm[]' '$cube_file')"
+        do
+            sed -i "/$rm_regex/di" $tmp_dir/client.conf.tpl
+        done
+
+        # Add some other lines
+        echo "# Custom" >> $tmp_dir/client.conf.tpl
+        jq --raw-output ".openvpn_add[]" "$cube_file" >> $tmp_dir/client.conf.tpl
+    fi
+
+    _ynh_panel_validate
+}
+
+#=================================================
+# OVERWRITING APPLY STEP 
+#=================================================
+ynh_panel_apply() {
+    
+    # Stop vpn client
+    touch /tmp/.ynh-vpnclient-stopped
+    systemctl stop ynh-vpnclient
+
+    _ynh_panel_apply
+
+    # Start vpn client
+    systemctl start ynh-vpnclient
+    rm -f /tmp/.ynh-vpnclient-stopped
+
+}
+
+#=================================================
+# GENERIC FINALIZATION
+#=================================================
+# Please don't change that code.
+# You can overwrite these functions if you need it
+#=================================================
+
+ynh_panel_init
+case $1 in
+  show) ynh_panel_show;;
+  apply) ynh_panel_validate && ynh_panel_apply;;
+esac
+