├── .editorconfig ├── .github └── FUNDING.yml ├── .gitignore ├── .php_cs ├── .scrutinizer.yml ├── .travis.yml ├── LICENSE ├── README.md ├── composer.json ├── config ├── openvpn-client.php └── openvpn-server.php ├── examples ├── client.php ├── client_laravel.php ├── client_v2.php ├── import.php ├── server.php └── server_v2.php ├── phpunit.xml ├── src ├── Config.php ├── Generator.php ├── Helpers.php ├── Import.php ├── Interfaces │ ├── ConfigInterface.php │ ├── GeneratorInterface.php │ └── ImportInterface.php ├── Laravel │ ├── Facade.php │ ├── ServiceProvider.php │ └── Wrapper.php └── Types │ ├── Cert.php │ ├── Parameter.php │ ├── Push.php │ ├── Remote.php │ └── Route.php └── tests ├── ConfigTest.php ├── GeneratorTest.php ├── HelpersTest.php ├── ImportTest.php ├── sample.crt └── server.ovpn /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | indent_size = 4 7 | indent_style = space 8 | insert_final_newline = false 9 | max_line_length = 140 10 | tab_width = 4 11 | ij_continuation_indent_size = 8 12 | ij_formatter_off_tag = @formatter:off 13 | ij_formatter_on_tag = @formatter:on 14 | ij_formatter_tags_enabled = false 15 | ij_smart_tabs = false 16 | ij_wrap_on_typing = false 17 | 18 | [{*.module,*.hphp,*.phtml,*.php5,*.php4,*.php,*.ctp,*.inc}] 19 | max_line_length = 140 20 | ij_continuation_indent_size = 4 21 | ij_php_align_assignments = true 22 | ij_php_align_class_constants = true 23 | ij_php_align_group_field_declarations = true 24 | ij_php_align_inline_comments = true 25 | ij_php_align_key_value_pairs = true 26 | ij_php_align_multiline_array_initializer_expression = false 27 | ij_php_align_multiline_binary_operation = false 28 | ij_php_align_multiline_chained_methods = false 29 | ij_php_align_multiline_extends_list = false 30 | ij_php_align_multiline_for = true 31 | ij_php_align_multiline_parameters = false 32 | ij_php_align_multiline_parameters_in_calls = false 33 | ij_php_align_multiline_ternary_operation = false 34 | ij_php_align_phpdoc_comments = true 35 | ij_php_align_phpdoc_param_names = true 36 | ij_php_anonymous_brace_style = end_of_line 37 | ij_php_api_weight = 28 38 | ij_php_array_initializer_new_line_after_left_brace = true 39 | ij_php_array_initializer_right_brace_on_new_line = true 40 | ij_php_array_initializer_wrap = on_every_item 41 | ij_php_assignment_wrap = off 42 | ij_php_author_weight = 28 43 | ij_php_binary_operation_sign_on_next_line = false 44 | ij_php_binary_operation_wrap = off 45 | ij_php_blank_lines_after_class_header = 0 46 | ij_php_blank_lines_after_function = 1 47 | ij_php_blank_lines_after_imports = 1 48 | ij_php_blank_lines_after_opening_tag = 0 49 | ij_php_blank_lines_after_package = 1 50 | ij_php_blank_lines_around_class = 1 51 | ij_php_blank_lines_around_constants = 0 52 | ij_php_blank_lines_around_field = 0 53 | ij_php_blank_lines_around_method = 1 54 | ij_php_blank_lines_before_class_end = 0 55 | ij_php_blank_lines_before_imports = 1 56 | ij_php_blank_lines_before_method_body = 0 57 | ij_php_blank_lines_before_package = 1 58 | ij_php_blank_lines_before_return_statement = 0 59 | ij_php_blank_lines_between_imports = 0 60 | ij_php_block_brace_style = end_of_line 61 | ij_php_call_parameters_new_line_after_left_paren = false 62 | ij_php_call_parameters_right_paren_on_new_line = false 63 | ij_php_call_parameters_wrap = normal 64 | ij_php_catch_on_new_line = false 65 | ij_php_category_weight = 28 66 | ij_php_class_brace_style = next_line 67 | ij_php_comma_after_last_array_element = false 68 | ij_php_concat_spaces = true 69 | ij_php_copyright_weight = 28 70 | ij_php_deprecated_weight = 28 71 | ij_php_do_while_brace_force = always 72 | ij_php_else_if_style = combine 73 | ij_php_else_on_new_line = false 74 | ij_php_example_weight = 28 75 | ij_php_extends_keyword_wrap = off 76 | ij_php_extends_list_wrap = off 77 | ij_php_fields_default_visibility = private 78 | ij_php_filesource_weight = 28 79 | ij_php_finally_on_new_line = false 80 | ij_php_for_brace_force = always 81 | ij_php_for_statement_new_line_after_left_paren = false 82 | ij_php_for_statement_right_paren_on_new_line = false 83 | ij_php_for_statement_wrap = off 84 | ij_php_force_short_declaration_array_style = true 85 | ij_php_global_weight = 28 86 | ij_php_group_use_wrap = on_every_item 87 | ij_php_if_brace_force = always 88 | ij_php_if_lparen_on_next_line = false 89 | ij_php_if_rparen_on_next_line = false 90 | ij_php_ignore_weight = 28 91 | ij_php_import_sorting = alphabetic 92 | ij_php_indent_break_from_case = true 93 | ij_php_indent_case_from_switch = true 94 | ij_php_indent_code_in_php_tags = false 95 | ij_php_internal_weight = 28 96 | ij_php_keep_blank_lines_after_lbrace = 2 97 | ij_php_keep_blank_lines_before_right_brace = 2 98 | ij_php_keep_blank_lines_in_code = 2 99 | ij_php_keep_blank_lines_in_declarations = 2 100 | ij_php_keep_control_statement_in_one_line = true 101 | ij_php_keep_first_column_comment = true 102 | ij_php_keep_indents_on_empty_lines = false 103 | ij_php_keep_line_breaks = true 104 | ij_php_keep_rparen_and_lbrace_on_one_line = true 105 | ij_php_keep_simple_methods_in_one_line = false 106 | ij_php_lambda_brace_style = end_of_line 107 | ij_php_license_weight = 28 108 | ij_php_line_comment_add_space = false 109 | ij_php_line_comment_at_first_column = true 110 | ij_php_link_weight = 28 111 | ij_php_lower_case_boolean_const = true 112 | ij_php_lower_case_null_const = true 113 | ij_php_method_brace_style = next_line 114 | ij_php_method_call_chain_wrap = off 115 | ij_php_method_parameters_new_line_after_left_paren = true 116 | ij_php_method_parameters_right_paren_on_new_line = true 117 | ij_php_method_parameters_wrap = on_every_item 118 | ij_php_method_weight = 28 119 | ij_php_modifier_list_wrap = false 120 | ij_php_multiline_chained_calls_semicolon_on_new_line = false 121 | ij_php_namespace_brace_style = 1 122 | ij_php_null_type_position = in_the_end 123 | ij_php_package_weight = 28 124 | ij_php_param_weight = 0 125 | ij_php_parentheses_expression_new_line_after_left_paren = false 126 | ij_php_parentheses_expression_right_paren_on_new_line = false 127 | ij_php_phpdoc_blank_line_before_tags = true 128 | ij_php_phpdoc_blank_lines_around_parameters = true 129 | ij_php_phpdoc_keep_blank_lines = true 130 | ij_php_phpdoc_param_spaces_between_name_and_description = 1 131 | ij_php_phpdoc_param_spaces_between_tag_and_type = 1 132 | ij_php_phpdoc_param_spaces_between_type_and_name = 1 133 | ij_php_phpdoc_use_fqcn = true 134 | ij_php_phpdoc_wrap_long_lines = false 135 | ij_php_place_assignment_sign_on_next_line = false 136 | ij_php_place_parens_for_constructor = 0 137 | ij_php_property_read_weight = 28 138 | ij_php_property_weight = 28 139 | ij_php_property_write_weight = 28 140 | ij_php_return_type_on_new_line = false 141 | ij_php_return_weight = 1 142 | ij_php_see_weight = 28 143 | ij_php_since_weight = 28 144 | ij_php_sort_phpdoc_elements = true 145 | ij_php_space_after_colon = true 146 | ij_php_space_after_colon_in_return_type = true 147 | ij_php_space_after_comma = true 148 | ij_php_space_after_for_semicolon = true 149 | ij_php_space_after_quest = true 150 | ij_php_space_after_type_cast = true 151 | ij_php_space_after_unary_not = false 152 | ij_php_space_before_array_initializer_left_brace = false 153 | ij_php_space_before_catch_keyword = true 154 | ij_php_space_before_catch_left_brace = true 155 | ij_php_space_before_catch_parentheses = true 156 | ij_php_space_before_class_left_brace = true 157 | ij_php_space_before_closure_left_parenthesis = true 158 | ij_php_space_before_colon = true 159 | ij_php_space_before_colon_in_return_type = false 160 | ij_php_space_before_comma = false 161 | ij_php_space_before_do_left_brace = true 162 | ij_php_space_before_else_keyword = true 163 | ij_php_space_before_else_left_brace = true 164 | ij_php_space_before_finally_keyword = true 165 | ij_php_space_before_finally_left_brace = true 166 | ij_php_space_before_for_left_brace = true 167 | ij_php_space_before_for_parentheses = true 168 | ij_php_space_before_for_semicolon = false 169 | ij_php_space_before_if_left_brace = true 170 | ij_php_space_before_if_parentheses = true 171 | ij_php_space_before_method_call_parentheses = false 172 | ij_php_space_before_method_left_brace = true 173 | ij_php_space_before_method_parentheses = false 174 | ij_php_space_before_quest = true 175 | ij_php_space_before_switch_left_brace = true 176 | ij_php_space_before_switch_parentheses = true 177 | ij_php_space_before_try_left_brace = true 178 | ij_php_space_before_unary_not = false 179 | ij_php_space_before_while_keyword = true 180 | ij_php_space_before_while_left_brace = true 181 | ij_php_space_before_while_parentheses = true 182 | ij_php_space_between_ternary_quest_and_colon = false 183 | ij_php_spaces_around_additive_operators = true 184 | ij_php_spaces_around_arrow = false 185 | ij_php_spaces_around_assignment_in_declare = false 186 | ij_php_spaces_around_assignment_operators = true 187 | ij_php_spaces_around_bitwise_operators = true 188 | ij_php_spaces_around_equality_operators = true 189 | ij_php_spaces_around_logical_operators = true 190 | ij_php_spaces_around_multiplicative_operators = true 191 | ij_php_spaces_around_null_coalesce_operator = true 192 | ij_php_spaces_around_relational_operators = true 193 | ij_php_spaces_around_shift_operators = true 194 | ij_php_spaces_around_unary_operator = false 195 | ij_php_spaces_around_var_within_brackets = false 196 | ij_php_spaces_within_array_initializer_braces = false 197 | ij_php_spaces_within_brackets = false 198 | ij_php_spaces_within_catch_parentheses = false 199 | ij_php_spaces_within_for_parentheses = false 200 | ij_php_spaces_within_if_parentheses = false 201 | ij_php_spaces_within_method_call_parentheses = false 202 | ij_php_spaces_within_method_parentheses = false 203 | ij_php_spaces_within_parentheses = false 204 | ij_php_spaces_within_short_echo_tags = true 205 | ij_php_spaces_within_switch_parentheses = false 206 | ij_php_spaces_within_while_parentheses = false 207 | ij_php_special_else_if_treatment = false 208 | ij_php_subpackage_weight = 28 209 | ij_php_ternary_operation_signs_on_next_line = false 210 | ij_php_ternary_operation_wrap = off 211 | ij_php_throws_weight = 2 212 | ij_php_todo_weight = 28 213 | ij_php_unknown_tag_weight = 28 214 | ij_php_upper_case_boolean_const = false 215 | ij_php_upper_case_null_const = false 216 | ij_php_uses_weight = 28 217 | ij_php_var_weight = 28 218 | ij_php_variable_naming_style = mixed 219 | ij_php_version_weight = 28 220 | ij_php_while_brace_force = always 221 | ij_php_while_on_new_line = false 222 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | patreon: efreelancer 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | /vendor/ 3 | /composer.lock 4 | /examples/server.conf 5 | /.php_cs.cache 6 | /.phpunit.result.cache 7 | -------------------------------------------------------------------------------- /.php_cs: -------------------------------------------------------------------------------- 1 | in(__DIR__ . DIRECTORY_SEPARATOR . 'tests') 5 | ->in(__DIR__ . DIRECTORY_SEPARATOR . 'src') 6 | ->append(['.php_cs']); 7 | 8 | $rules = [ 9 | '@Symfony' => true, 10 | 'phpdoc_no_empty_return' => false, 11 | 'phpdoc_summary' => false, 12 | 'no_superfluous_phpdoc_tags' => false, 13 | 'phpdoc_separation' => false, 14 | 'phpdoc_trim' => false, 15 | 'phpdoc_align' => false, 16 | 'array_syntax' => ['syntax' => 'short'], 17 | 'yoda_style' => true, 18 | 'binary_operator_spaces' => false, 19 | 'concat_space' => ['spacing' => 'one'], 20 | 'not_operator_with_space' => false, 21 | ]; 22 | 23 | $rules['increment_style'] = ['style' => 'post']; 24 | 25 | return PhpCsFixer\Config::create() 26 | ->setUsingCache(true) 27 | ->setRules($rules) 28 | ->setFinder($finder); 29 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | filter: 2 | excluded_paths: 3 | - ".github/" 4 | - "tests/" 5 | - "vendor/" 6 | - "config/" 7 | 8 | checks: 9 | php: true 10 | 11 | coding_style: 12 | php: 13 | spaces: 14 | around_operators: 15 | concatenation: true 16 | 17 | build: 18 | nodes: 19 | tests: 20 | dependencies: 21 | before: 22 | - sudo apt-get update && sudo apt-get install -fyqq git ssh 23 | tests: 24 | override: 25 | - command: 'php ./vendor/bin/phpunit --coverage-clover=coverage-clover' 26 | coverage: 27 | file: 'coverage-clover' 28 | format: 'php-clover' 29 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - '7.2' 5 | - '7.3' 6 | - '7.4' 7 | 8 | before_script: 9 | - composer self-update 10 | - composer install --no-interaction --dev 11 | 12 | script: 13 | - vendor/bin/phpunit --coverage-clover=coverage.clover 14 | 15 | after_script: 16 | - wget https://scrutinizer-ci.com/ocular.phar 17 | - php ocular.phar code-coverage:upload --format=php-clover coverage.clover 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Coder 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Latest Stable Version](https://poser.pugx.org/evilfreelancer/openvpn-php/v/stable)](https://packagist.org/packages/evilfreelancer/openvpn-php) 2 | [![Build Status](https://travis-ci.org/evilfreelancer/openvpn-php.svg?branch=master)](https://travis-ci.org/EvilFreelancer/openvpn-php) 3 | [![Total Downloads](https://poser.pugx.org/evilfreelancer/openvpn-php/downloads)](https://packagist.org/packages/evilfreelancer/openvpn-php) 4 | [![License](https://poser.pugx.org/evilfreelancer/openvpn-php/license)](https://packagist.org/packages/evilfreelancer/openvpn-php) 5 | [![Code Climate](https://codeclimate.com/github/EvilFreelancer/openvpn-php/badges/gpa.svg)](https://codeclimate.com/github/EvilFreelancer/openvpn-php) 6 | [![Code Coverage](https://scrutinizer-ci.com/g/EvilFreelancer/openvpn-php/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/EvilFreelancer/openvpn-php/?branch=master) 7 | [![Scrutinizer CQ](https://scrutinizer-ci.com/g/EvilFreelancer/openvpn-php/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/EvilFreelancer/openvpn-php/) 8 | 9 | # OpenVPN config manager 10 | 11 | OpenVPN configuration manager written on PHP. 12 | 13 | composer require evilfreelancer/openvpn-php 14 | 15 | By the way, OpenVPN library support Laravel framework, details [here](#laravel-framework-support). 16 | 17 | ## How to use 18 | 19 | It's very simple, you need to set the required parameters, then 20 | generate the config and voila, everything is done. 21 | 22 | More examples [here](examples). 23 | 24 | ### Write new config in OOP style 25 | 26 | ```php 27 | require_once __DIR__ . '/../vendor/autoload.php'; 28 | 29 | // Config object 30 | $config = new \OpenVPN\Config(); 31 | 32 | // Set server options 33 | $config->dev = 'tun'; 34 | $config->proto = 'tcp'; 35 | $config->port = 1194; 36 | $config->resolvRetry = 'infinite'; 37 | $config->cipher = 'AES-256-CBC'; 38 | $config->redirectGateway = true; 39 | $config->server = '10.8.0.0 255.255.255.0'; 40 | $config->keepalive = '10 120'; 41 | $config->renegSec = 18000; 42 | $config->user = 'nobody'; 43 | $config->group = 'nogroup'; 44 | $config->persistKey = true; 45 | $config->persistTun = true; 46 | $config->compLzo = true; 47 | $config->verb = 3; 48 | $config->mute = 20; 49 | $config->status = '/var/log/openvpn/status.log'; 50 | $config->logAppend = '/var/log/openvpn/openvpn.log'; 51 | $config->clientConfigDir = 'ccd'; 52 | $config->scriptSecurity = 3; 53 | $config->usernameAsCommonName = true; 54 | $config->verifyClientCert = 'none'; 55 | 56 | // Set routes which will be used by server after starting 57 | $config->setRoutes([ 58 | '10.1.1.0 255.255.255.0', 59 | '10.1.2.0 255.255.255.0', 60 | '10.1.3.0 255.255.255.0', 61 | ]); 62 | 63 | // Set additional certificates of server 64 | $config->setCerts([ 65 | 'ca' => '/etc/openvpn/keys/ca.crt', 66 | 'cert' => '/etc/openvpn/keys/issued/server.crt', 67 | ]); // You can embed certificates into config by adding true as second parameter of setCerts method 68 | 69 | // Another way for adding certificates 70 | $config 71 | ->setCert('key', '/etc/openvpn/keys/private/server.key') 72 | ->setCert('dh', '/etc/openvpn/keys/dh.pem'); 73 | 74 | // Set pushes which will be passed to client 75 | $config->setPushes([ 76 | // Additional routes, which clients will see 77 | 'route 10.1.2.0 255.255.255.0', 78 | 'route 10.1.3.0 255.255.255.0', 79 | 'route 10.1.4.0 255.255.255.0', 80 | 81 | // Replace default gateway, all client's traffic will be routed via VPN 82 | 'redirect-gateway def1', 83 | 84 | // Prepend additional DNS addresses 85 | 'dhcp-option DNS 8.8.8.8', 86 | 'dhcp-option DNS 8.8.4.4', 87 | ]); 88 | 89 | // Generate config by options 90 | echo $config->generate(); 91 | ``` 92 | 93 | ### Import existing OpenVPN config 94 | 95 | For example, you have `server.conf`, to import this file you need create 96 | `\OpenVPN\Import` object and specify a name of your config file. 97 | 98 | ```php 99 | read('server.conf'); 108 | 109 | // Parse configuration and return "\OpenVPN\Config" object 110 | $config = $import->parse(); 111 | ``` 112 | 113 | In `$config` variable will be `\OpenVPN\Config` object. 114 | 115 | ### Client config example 116 | 117 | For making client configuration you need just add required parameters 118 | and generate the config: 119 | 120 | ```php 121 | client(); 129 | $config->dev = 'tun'; 130 | $config->proto = 'tcp'; 131 | $config->resolvRetry = 'infinite'; 132 | $config->cipher = 'AES-256-CB'; 133 | $config->redirectGateway = true; 134 | $config->keyDirection = 1; 135 | $config->remoteCertTls = 'server'; 136 | $config->authUserPass = true; 137 | $config->authNocache = true; 138 | $config->nobind = true; 139 | $config->persistKey = true; 140 | $config->persistTun = true; 141 | $config->compLzo = true; 142 | $config->verb = 3; 143 | $config->httpProxy = 'proxy-http.example.com 3128'; 144 | 145 | // Set multiple remote servers 146 | $config->setRemotes([ 147 | 'vpn1.example.com 1194', 148 | 'vpn2.example.com 11194' 149 | ]); 150 | 151 | // Set single remote 152 | $config->setRemote('vpn1.example.com 1194'); 153 | 154 | // Or set remote server as parameter of object 155 | $config->remote = 'vpn.example.com 1194'; 156 | 157 | // Set additional certificates of client 158 | $config->setCerts([ 159 | 'ca' => '/etc/openvpn/keys/ca.crt', 160 | 'cert' => '/etc/openvpn/keys/issued/client1.crt', 161 | 'key' => '/etc/openvpn/keys/private/client1.key', 162 | ], true); // true - mean embed certificates into config, false by default 163 | 164 | // Generate config by options 165 | echo $config->generate(); 166 | ``` 167 | 168 | ### Downloadable config 169 | 170 | Just a simple usage example: 171 | 172 | ```php 173 | header('Content-Type:text/plain'); 174 | header('Content-Disposition: attachment; filename=client.ovpn'); 175 | header('Pragma: no-cache'); 176 | header('Expires: 0'); 177 | 178 | echo $config->generate(); 179 | die(); 180 | ``` 181 | 182 | ## Laravel framework support 183 | 184 | This library is optimized for usage as normal Laravel package, all functional is available via `\OpenVPN` facade, 185 | for access to (for example) client object you need: 186 | 187 | ```php 188 | // Config og client object 189 | $config = \OpenVPN::client([ 190 | 'dev' => 'tun', 191 | 'proto' => 'tcp', 192 | 'resolv-retry' => 'infinite', 193 | 'cipher' => 'AES-256-CB', 194 | 'redirect-gateway' => true, 195 | 'key-direction' => 1, 196 | 'remote-cert-tls' => 'server', 197 | 'auth-user-pass' => true, 198 | 'auth-nocache' => true, 199 | 'persist-key' => true, 200 | 'persist-tun' => true, 201 | 'comp-lzo' => true, 202 | 'verb' => 3, 203 | ]); 204 | 205 | // Another way for change values 206 | $config->set('verb', 3); 207 | $config->set('nobind'); 208 | 209 | // Yet another way for change values via magic methods 210 | $config->remote = 'vpn.example.com 1194'; 211 | $config->httpProxy = 'proxy-http.example.com 3128'; 212 | 213 | // Set multiple remote servers 214 | $config->setRemotes([ 215 | 'vpn1.example.com 1194', 216 | 'vpn2.example.com 11194' 217 | ]); 218 | 219 | // Set additional certificates of client 220 | $config->setCerts([ 221 | 'ca' => '/etc/openvpn/keys/ca.crt', 222 | 'cert' => '/etc/openvpn/keys/issued/client1.crt', 223 | 'key' => '/etc/openvpn/keys/private/client1.key', 224 | ], true); // true mean embed certificates into config, false by default 225 | 226 | // Generate config by options 227 | echo $config->generate(); 228 | ``` 229 | 230 | It will read `openvpn-client.php` configuration from `config` folder (if it was published of course), then merge your parameters to this 231 | array and in results you will see the `\OpenVPN\Config` object. 232 | 233 | ### List of available methods 234 | 235 | * `\OpenVPN::server(array $parameters = [])` - Will return `\OpenVPN\Config` object with settings loaded from `openvpn-server.php` 236 | * `\OpenVPN::client(array $parameters = [])` - Will return `\OpenVPN\Config` object with settings loaded from `openvpn-client.php` 237 | * `\OpenVPN::importer(string $filename = null, bool $isContent = false)` - Will return `\OpenVPN\Import` object, with help of this object 238 | you may read OpenVPN configuration of your server or client 239 | * `\OpenVPN::generator(\OpenVPN\Config $config)` - Will return `\OpenVPN\Generator` object with `->generate()` method, which may used 240 | for render OpenVPN configuration by parameters from Config object 241 | 242 | ### Installation 243 | 244 | The package's service provider will automatically register its service provider. 245 | 246 | Publish the `openvpn-server.php` and `openvpn-client.php` configuration files: 247 | 248 | ```sh 249 | php artisan vendor:publish --provider="OpenVPN\Laravel\ServiceProvider" 250 | ``` 251 | 252 | ## Testing 253 | 254 | Before you begin need to install `dev` dependencies 255 | 256 | ```shell script 257 | composer install --dev 258 | ``` 259 | 260 | Then run tests 261 | 262 | ```shell script 263 | composer test 264 | 265 | # which same as 266 | composer test:lint 267 | composer test:unit 268 | ``` 269 | 270 | or 271 | 272 | ```shell script 273 | ./vendor/bin/phpunit 274 | ``` 275 | 276 | ## Links 277 | 278 | * [OpenVPN parameters](https://openvpn.net/index.php/open-source/documentation/manuals/65-openvpn-20x-manpage.html) - Full list of available parameters what can be used 279 | * [Laravel VPN Admin](https://github.com/Laravel-VPN-Admin) - Web interface for your VPN server 280 | * [OpenVPN Admin](https://github.com/Chocobozzz/OpenVPN-Admin) - Web interface for your OpenVPN server 281 | * [Docker for OpenVPN Admin](https://github.com/EvilFreelancer/docker-openvpn-admin) - Dockerized web panel together with OpenVPN 282 | * [PHP OpenVPN](https://github.com/paranic/openvpn) - Yet another library for generating OpenVPN config files 283 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "evilfreelancer/openvpn-php", 3 | "description": "OpenVPN config generator writen on PHP", 4 | "keywords": [ 5 | "openvpn", 6 | "config", 7 | "generate", 8 | "import", 9 | "laravel", 10 | "lumen", 11 | "plugin", 12 | "package" 13 | ], 14 | "license": "MIT", 15 | "type": "library", 16 | "authors": [ 17 | { 18 | "homepage": "http://drteam.rocks/", 19 | "email": "paul@drteam.rocks", 20 | "name": "Paul Rock", 21 | "role": "Developer" 22 | } 23 | ], 24 | "autoload": { 25 | "psr-4": { 26 | "OpenVPN\\": "./src/" 27 | } 28 | }, 29 | "autoload-dev": { 30 | "psr-4": { 31 | "Tests\\OpenVPN\\": "./tests/" 32 | } 33 | }, 34 | "require": { 35 | "php": "^7.2", 36 | "ext-mbstring": "*", 37 | "ext-json": "*" 38 | }, 39 | "require-dev": { 40 | "phpunit/phpunit": "^8.0", 41 | "larapack/dd": "^1.1", 42 | "friendsofphp/php-cs-fixer": "^2.16", 43 | "limedeck/phpunit-detailed-printer": "^5.0", 44 | "orchestra/testbench": "^4.0|^5.0" 45 | }, 46 | "suggest": { 47 | "evilfreelancer/easyrsa-php": "1.0.0" 48 | }, 49 | "config": { 50 | "sort-packages": true, 51 | "preferred-install": "dist" 52 | }, 53 | "scripts": { 54 | "test:lint": "php-cs-fixer fix -v --dry-run", 55 | "test:unit": "phpunit", 56 | "test": [ 57 | "@test:lint", 58 | "@test:unit" 59 | ] 60 | }, 61 | "extra": { 62 | "laravel": { 63 | "providers": [ 64 | "OpenVPN\\Laravel\\ServiceProvider" 65 | ], 66 | "aliases": { 67 | "OpenVPN": "OpenVPN\\Laravel\\Facade" 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /config/openvpn-client.php: -------------------------------------------------------------------------------- 1 | '192.168.1.10 1194', 10 | ]; 11 | -------------------------------------------------------------------------------- /config/openvpn-server.php: -------------------------------------------------------------------------------- 1 | '192.168.1.10', 10 | // 'port' => 1194 11 | ]; 12 | -------------------------------------------------------------------------------- /examples/client.php: -------------------------------------------------------------------------------- 1 | client() 10 | ->set('dev', 'tun') 11 | ->set('remote', 'vpn.example.com 1194') 12 | ->set('proto', 'tcp') 13 | ->set('resolv-retry', 'infinite') 14 | ->set('cipher', 'AES-256-CBC') 15 | ->set('redirect-gateway', true) 16 | ->set('ca', '/etc/openvpn/ca.crt') 17 | ->set('tls-auth', '/etc/openvpn/ta.key 0') 18 | ->set('key-direction', 1) 19 | ->set('remote-cert-tls', 'server') 20 | ->set('auth-user-pass', true) 21 | ->set('auth-nocache', true) 22 | ->set('nobind', true) 23 | ->set('persist-key', true) 24 | ->set('persist-tun', true) 25 | ->set('comp-lzo', true) 26 | ->set('verb', 3) 27 | ->set('http-proxy', 'proxy-http.example.com 3128'); 28 | 29 | // Generate config by options 30 | echo $config->generate(); 31 | -------------------------------------------------------------------------------- /examples/client_laravel.php: -------------------------------------------------------------------------------- 1 | 'tun', 7 | 'proto' => 'tcp', 8 | 'resolvRetry' => 'infinite', 9 | 'cipher' => 'AES-256-CB', 10 | 'redirect-gateway' => true, 11 | 'key-direction' => 1, 12 | 'remote-cert-tls' => 'server', 13 | 'auth-user-pass' => true, 14 | 'auth-nocache' => true, 15 | 'nobind' => true, 16 | 'persist-key' => true, 17 | 'persist-tun' => true, 18 | 'comp-lzo' => true, 19 | 'verb' => 3, 20 | ]); 21 | 22 | // Another way for change values 23 | $config->remote = 'vpn.example.com 1194'; 24 | $config->httpProxy = 'proxy-http.example.com 3128'; 25 | 26 | // Set additional certificates of client 27 | $config->setCerts([ 28 | 'ca' => '/etc/openvpn/keys/ca.crt', 29 | 'cert' => '/etc/openvpn/keys/issued/client1.crt', 30 | 'key' => '/etc/openvpn/keys/private/client1.key', 31 | ], true); // true mean embed certificates into config, false by default 32 | 33 | // Generate config by options 34 | echo $config->generate(); 35 | -------------------------------------------------------------------------------- /examples/client_v2.php: -------------------------------------------------------------------------------- 1 | client(); 9 | $config->dev = 'tun'; 10 | $config->proto = 'tcp'; 11 | $config->resolvRetry = 'infinite'; 12 | $config->cipher = 'AES-256-CB'; 13 | $config->redirectGateway = true; 14 | $config->keyDirection = 1; 15 | $config->remoteCertTls = 'server'; 16 | $config->authUserPass = true; 17 | $config->authNocache = true; 18 | $config->nobind = true; 19 | $config->persistKey = true; 20 | $config->persistTun = true; 21 | $config->compLzo = true; 22 | $config->verb = 3; 23 | $config->httpProxy = 'proxy-http.example.com 3128'; 24 | 25 | $config->setRemotes([ 26 | 'vpn1.example.com 1194', 27 | 'vpn2.example.com 11194' 28 | ]); 29 | 30 | //// Set additional certificates of client 31 | //$config->setCerts([ 32 | // 'ca' => '/etc/openvpn/keys/ca.crt', 33 | // 'cert' => '/etc/openvpn/keys/issued/client1.crt', 34 | // 'key' => '/etc/openvpn/keys/private/client1.key', 35 | //], true); 36 | 37 | // Generate config by options 38 | echo $config->generate() . PHP_EOL; 39 | -------------------------------------------------------------------------------- /examples/import.php: -------------------------------------------------------------------------------- 1 | parse(); 10 | print_r($config); 11 | 12 | // Generate config by options 13 | echo $config->generate(); 14 | -------------------------------------------------------------------------------- /examples/server.php: -------------------------------------------------------------------------------- 1 | set('dev', 'tun') 10 | ->set('proto', 'tcp') 11 | ->set('port', '1194') 12 | ->set('resolv-retry', 'infinite') 13 | ->set('cipher', 'AES-256-CBC') 14 | ->set('redirect-gateway', 'true') 15 | ->set('server', '10.8.0.0 255.255.255.0') 16 | ->set('keepalive', '10 120') 17 | ->set('reneg-sec', 18000) 18 | ->set('user', 'nobody') 19 | ->set('group', 'nogroup') 20 | ->set('persist-key', true) 21 | ->set('persist-tun', true) 22 | ->set('compLzo', true) 23 | ->set('verb', 3) 24 | ->set('mute', 20) 25 | ->set('status', '/var/log/openvpn/status.log') 26 | ->set('log-append', '/var/log/openvpn/openvpn.log') 27 | ->set('client-config-dir', 'ccd') 28 | ->set('script-security', 3) 29 | ->set('username-as-common-name', true) 30 | ->set('verify-client-cert', 'none') 31 | ->setCert('ca', '/etc/openvpn/ca.crt') 32 | ->setCert('cert', '/etc/openvpn/server.crt') 33 | ->setCert('key', '/etc/openvpn/server.key') 34 | ->setCert('dh', '/etc/openvpn/dh') 35 | ->setCert('tls-auth', '/etc/openvpn/ta.key 0') 36 | ->setPush('redirect-gateway def1') 37 | ->setPush('dhcp-option DNS 8.8.8.8') 38 | ->setPush('dhcp-option DNS 8.8.4.4'); 39 | 40 | // Generate config by options 41 | echo $config->generate(); 42 | -------------------------------------------------------------------------------- /examples/server_v2.php: -------------------------------------------------------------------------------- 1 | dev = 'tun'; 9 | $config->proto = 'tcp'; 10 | $config->local = 'vpn.example.com'; 11 | $config->port = 1194; 12 | $config->resolvRetry = 'infinite'; 13 | $config->cipher = 'AES-256-CBC'; 14 | $config->redirectGateway = true; 15 | $config->server = '10.8.0.0 255.255.255.0'; 16 | $config->keepalive = '10 120'; 17 | $config->renegSec = 18000; 18 | $config->user = 'nobody'; 19 | $config->group = 'nogroup'; 20 | $config->persistKey = true; 21 | $config->persistTun = true; 22 | $config->compLzo = true; 23 | $config->verb = 3; 24 | $config->mute = 20; 25 | $config->status = '/var/log/openvpn/status.log'; 26 | $config->logAppend = '/var/log/openvpn/openvpn.log'; 27 | $config->clientConfigDir = 'ccd'; 28 | $config->scriptSecurity = 3; 29 | $config->usernameAsCommonName = true; 30 | $config->verifyClientCert = 'none'; 31 | $config->authUserPassVerify = 'your_script.sh via-file'; 32 | $config->duplicateCn = true; 33 | 34 | // Set routes which will be used by server after starting 35 | $config->setRoutes([ 36 | '10.1.1.0 255.255.255.0', 37 | '10.1.2.0 255.255.255.0', 38 | '10.1.3.0 255.255.255.0', 39 | '10.1.4.0 255.255.255.0', 40 | '10.1.5.0 255.255.255.0', 41 | ]); 42 | 43 | // Set additional certificates of server 44 | $config->setCerts([ 45 | 'ca' => '/etc/openvpn/keys/ca.crt', 46 | 'cert' => '/etc/openvpn/keys/issued/server.crt', 47 | ]); // You can embed certificates into config by adding true as second parameter of setCerts method 48 | 49 | // Another way for adding certificates 50 | $config 51 | ->setCert('key', '/etc/openvpn/keys/private/server.key') 52 | ->setCert('dh', '/etc/openvpn/keys/dh.pem'); 53 | 54 | // Set pushes which will be passed to client 55 | $config->setPushes([ 56 | // Additional routes, which clients will see 57 | 'route 10.1.2.0 255.255.255.0', 58 | 'route 10.1.3.0 255.255.255.0', 59 | 'route 10.1.4.0 255.255.255.0', 60 | 61 | // Replace default gateway, all client's traffic will be routed via VPN 62 | 'redirect-gateway def1', 63 | 64 | // Prepend additional DNS addresses 65 | 'dhcp-option DNS 8.8.8.8', 66 | 'dhcp-option DNS 8.8.4.4', 67 | ]); 68 | 69 | // Generate config by options 70 | echo $config->generate('json'); 71 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 14 | ./src 15 | 16 | ./tests 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | ./tests/ 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | setParams($parameters); 130 | } 131 | 132 | /** 133 | * Alias for client line of config 134 | * 135 | * @return \OpenVPN\Interfaces\ConfigInterface 136 | */ 137 | public function client(): ConfigInterface 138 | { 139 | return $this->set('client'); 140 | } 141 | 142 | /** 143 | * Import content of all listed certificates 144 | * 145 | * @return void 146 | */ 147 | public function loadCertificates(): void 148 | { 149 | foreach ($this->certs as &$cert) { 150 | $cert['content'] = rtrim(file_get_contents($cert['path'])); 151 | } 152 | } 153 | 154 | /** 155 | * Add new cert into the configuration 156 | * 157 | * @param string $type Type of certificate [ca, cert, key, dh, tls-auth] 158 | * @param string $path Absolute or relative path to certificate or content of this file 159 | * @param bool|null $isContent If true, then script will try to load file from dist by $path 160 | * 161 | * @return \OpenVPN\Interfaces\ConfigInterface 162 | * @throws \RuntimeException 163 | */ 164 | public function setCert(string $type, string $path, bool $isContent = null): ConfigInterface 165 | { 166 | $type = mb_strtolower($type); 167 | Helpers::isCertAllowed($type); 168 | if (true === $isContent) { 169 | $this->certs[$type]['content'] = $path; 170 | } else { 171 | $this->certs[$type]['path'] = $path; 172 | } 173 | 174 | return $this; 175 | } 176 | 177 | /** 178 | * Return information about specified certificate 179 | * 180 | * @param string $type 181 | * 182 | * @return array 183 | * @throws \RuntimeException 184 | */ 185 | public function getCert(string $type): array 186 | { 187 | $type = mb_strtolower($type); 188 | Helpers::isCertAllowed($type); 189 | 190 | return $this->certs[$type] ?? []; 191 | } 192 | 193 | /** 194 | * Append new push into the array 195 | * 196 | * @param string $line String with line which must be pushed 197 | * 198 | * @return \OpenVPN\Interfaces\ConfigInterface 199 | */ 200 | public function setPush(string $line): ConfigInterface 201 | { 202 | $this->pushes[] = trim($line, '"'); 203 | 204 | return $this; 205 | } 206 | 207 | /** 208 | * Remove route line from push array 209 | * 210 | * @param string $line String with line which must be pushed 211 | * 212 | * @return \OpenVPN\Interfaces\ConfigInterface 213 | */ 214 | public function unsetPush(string $line): ConfigInterface 215 | { 216 | unset($this->pushes[$line]); 217 | 218 | return $this; 219 | } 220 | 221 | /** 222 | * Append new route into the array 223 | * 224 | * @param string $line String with route 225 | * 226 | * @return \OpenVPN\Interfaces\ConfigInterface 227 | */ 228 | public function setRoute(string $line): ConfigInterface 229 | { 230 | $this->routes[] = trim($line, '"'); 231 | 232 | return $this; 233 | } 234 | 235 | /** 236 | * Remove route line from routes array 237 | * 238 | * @param string $line String with route 239 | * 240 | * @return \OpenVPN\Interfaces\ConfigInterface 241 | */ 242 | public function unsetRoute(string $line): ConfigInterface 243 | { 244 | unset($this->routes[$line]); 245 | 246 | return $this; 247 | } 248 | 249 | /** 250 | * Append new push into the array 251 | * 252 | * @param string $line String with line which must be added as remote 253 | * 254 | * @return \OpenVPN\Interfaces\ConfigInterface 255 | */ 256 | public function setRemote(string $line): ConfigInterface 257 | { 258 | $this->remotes[] = trim($line, '"'); 259 | 260 | return $this; 261 | } 262 | 263 | /** 264 | * Remove remote line from remotes array 265 | * 266 | * @param string $line String with line which must be added as remote 267 | * 268 | * @return \OpenVPN\Interfaces\ConfigInterface 269 | */ 270 | public function unsetRemote(string $line): ConfigInterface 271 | { 272 | unset($this->remotes[$line]); 273 | 274 | return $this; 275 | } 276 | 277 | /** 278 | * Add some new parameter to the list of parameters 279 | * 280 | * @param string $name Name of parameter 281 | * @param string|bool|null $value Value of parameter 282 | * 283 | * @return \OpenVPN\Interfaces\ConfigInterface 284 | * @example $this->add('client')->add('remote', 'vpn.example.com'); 285 | */ 286 | public function set(string $name, $value = null): ConfigInterface 287 | { 288 | $name = mb_strtolower($name); 289 | 290 | // Check if key is certificate or push, or classic parameter 291 | if (in_array($name, self::ALLOWED_TYPES_OF_CERTS, true)) { 292 | return $this->setCert($name, $value); 293 | } 294 | 295 | // If is push then use add push method 296 | if ($name === 'remote') { 297 | return $this->setRemote($value); 298 | } 299 | 300 | // If is push then use add push method 301 | if ($name === 'push') { 302 | return $this->setPush($value); 303 | } 304 | 305 | // If is push then use add push method 306 | if ($name === 'route') { 307 | return $this->setRoute($value); 308 | } 309 | 310 | // Check if provided value is boolean and if it's true, then set null (that mean parameter without value) 311 | if (is_bool($value) && $value) { 312 | if ($value) { 313 | $value = null; 314 | } else { 315 | // If false then skip this step 316 | return $this; 317 | } 318 | } 319 | 320 | // Set new value 321 | $this->parameters[$name] = $value; 322 | 323 | return $this; 324 | } 325 | 326 | /** 327 | * Get some custom element 328 | * 329 | * @param string|null $name Name of parameter 330 | * 331 | * @return mixed 332 | */ 333 | public function get(string $name) 334 | { 335 | return $this->parameters[$name] ?? null; 336 | } 337 | 338 | /** 339 | * Generate config by parameters in memory 340 | * 341 | * @param string $type Type of generated config: raw (default), json 342 | * 343 | * @return array|string|null 344 | */ 345 | public function generate(string $type = 'raw') 346 | { 347 | $generator = new Generator($this); 348 | 349 | return $generator->generate($type); 350 | } 351 | 352 | /** 353 | * @param string $name 354 | * 355 | * @return bool 356 | */ 357 | public function __isset(string $name): bool 358 | { 359 | // Inform about deleting push 360 | if ($name === 'push') { 361 | throw new RuntimeException("Not possible to remove push, use 'unsetPush' instead"); 362 | } 363 | 364 | // Inform about deleting route 365 | if ($name === 'route') { 366 | throw new RuntimeException("Not possible to remove route, use 'unsetRoute' instead"); 367 | } 368 | 369 | // Inform about deleting route 370 | if ($name === 'remote') { 371 | throw new RuntimeException("Not possible to remove remote, use 'unsetRemote' instead"); 372 | } 373 | 374 | return isset($this->parameters[$name]); 375 | } 376 | 377 | /** 378 | * @param string $name 379 | * @param string|bool|integer|null $value 380 | */ 381 | public function __set(string $name, $value = null): void 382 | { 383 | $name = Helpers::decamelize($name); 384 | $this->set($name, $value); 385 | } 386 | 387 | /** 388 | * @param string $name 389 | * 390 | * @return string|bool|null 391 | */ 392 | public function __get(string $name) 393 | { 394 | return $this->get($name); 395 | } 396 | 397 | /** 398 | * Remove some parameter from array by name 399 | * 400 | * @param string $name Name of parameter 401 | * 402 | * @return void 403 | * @throws \RuntimeException 404 | */ 405 | public function __unset(string $name): void 406 | { 407 | // Inform about deleting push 408 | if ($name === 'push') { 409 | throw new RuntimeException("Not possible to remove push, use 'unsetPush' instead"); 410 | } 411 | 412 | // Inform about deleting route 413 | if ($name === 'route') { 414 | throw new RuntimeException("Not possible to remove route, use 'unsetRoute' instead"); 415 | } 416 | 417 | // Inform about deleting route 418 | if ($name === 'remote') { 419 | throw new RuntimeException("Not possible to remove remote, use 'unsetRemote' instead"); 420 | } 421 | 422 | // Check if key is certificate or push, or classic parameter 423 | if (in_array($name, self::ALLOWED_TYPES_OF_CERTS, true)) { 424 | $this->unsetCert($name); 425 | return; 426 | } 427 | 428 | // Update list of parameters 429 | $this->parameters = array_map( 430 | static function ($param) use ($name) { 431 | return ($param['name'] === $name) ? null : $param; 432 | }, 433 | $this->parameters 434 | ); 435 | } 436 | 437 | /** 438 | * Remove selected certificate from array 439 | * 440 | * @param string $type Type of certificate [ca, cert, key, dh, tls-auth] 441 | * 442 | * @return \OpenVPN\Interfaces\ConfigInterface 443 | * @throws \RuntimeException 444 | */ 445 | public function unsetCert(string $type): ConfigInterface 446 | { 447 | $type = mb_strtolower($type); 448 | Helpers::isCertAllowed($type); 449 | unset($this->certs[$type]); 450 | 451 | return $this; 452 | } 453 | 454 | /** 455 | * Set scope of certs 456 | * 457 | * @param \OpenVPN\Types\Cert[]|string[] $certs 458 | * @param bool $loadCertificates 459 | * 460 | * @return \OpenVPN\Interfaces\ConfigInterface 461 | */ 462 | public function setCerts(array $certs, bool $loadCertificates = false): ConfigInterface 463 | { 464 | // Pass list of certs from array to variable 465 | foreach ($certs as $type => $path) { 466 | $this->setCert($type, $path); 467 | } 468 | 469 | // If need to load content of files from disk 470 | if ($loadCertificates) { 471 | $this->loadCertificates(); 472 | } 473 | 474 | return $this; 475 | } 476 | 477 | /** 478 | * Set scope of unique pushes 479 | * 480 | * @param \OpenVPN\Types\Push[]|string[] $pushes 481 | * 482 | * @return \OpenVPN\Interfaces\ConfigInterface 483 | */ 484 | public function setPushes(array $pushes): ConfigInterface 485 | { 486 | foreach ($pushes as $push) { 487 | $this->setPush($push); 488 | } 489 | 490 | return $this; 491 | } 492 | 493 | /** 494 | * Set scope of unique routes 495 | * 496 | * @param \OpenVPN\Types\Route[]|string[] $routes 497 | * 498 | * @return \OpenVPN\Interfaces\ConfigInterface 499 | */ 500 | public function setRoutes(array $routes): ConfigInterface 501 | { 502 | foreach ($routes as $route) { 503 | $this->setRoute($route); 504 | } 505 | 506 | return $this; 507 | } 508 | 509 | /** 510 | * Set scope of unique remotes 511 | * 512 | * @param \OpenVPN\Types\Remote[]|string[] $remotes 513 | * 514 | * @return \OpenVPN\Interfaces\ConfigInterface 515 | */ 516 | public function setRemotes(array $remotes): ConfigInterface 517 | { 518 | foreach ($remotes as $remote) { 519 | $this->setRemote($remote); 520 | } 521 | 522 | return $this; 523 | } 524 | 525 | /** 526 | * Set scope of unique parameters 527 | * 528 | * @param \OpenVPN\Types\Parameter[]|string[] $parameters 529 | * 530 | * @return \OpenVPN\Interfaces\ConfigInterface 531 | */ 532 | public function setParams(array $parameters): ConfigInterface 533 | { 534 | foreach ($parameters as $name => $value) { 535 | $this->set($name, $value); 536 | } 537 | 538 | return $this; 539 | } 540 | 541 | /** 542 | * Export array of all certificates 543 | * 544 | * @return array 545 | */ 546 | public function getCerts(): array 547 | { 548 | return $this->certs; 549 | } 550 | 551 | /** 552 | * Export array of all pushes 553 | * 554 | * @return array 555 | */ 556 | public function getPushes(): array 557 | { 558 | return $this->pushes; 559 | } 560 | 561 | /** 562 | * Export array of all routes 563 | * 564 | * @return array 565 | */ 566 | public function getRoutes(): array 567 | { 568 | return $this->routes; 569 | } 570 | 571 | /** 572 | * Export array of all remotes 573 | * 574 | * @return array 575 | */ 576 | public function getRemotes(): array 577 | { 578 | return $this->remotes; 579 | } 580 | 581 | /** 582 | * Export array of all parameters 583 | * 584 | * @return array 585 | */ 586 | public function getParameters(): array 587 | { 588 | return $this->parameters; 589 | } 590 | } 591 | -------------------------------------------------------------------------------- /src/Generator.php: -------------------------------------------------------------------------------- 1 | config = $config; 29 | } 30 | 31 | /** 32 | * Generate config in array format 33 | * 34 | * @return array 35 | */ 36 | private function generateArray(): array 37 | { 38 | // Init the variable 39 | $config = []; 40 | 41 | // Basic parameters first 42 | foreach ($this->config->getParameters() as $key => $value) { 43 | $config[] = $key . ($value !== '' ? ' ' . $value : ''); 44 | } 45 | 46 | // Get all what need for normal work 47 | $pushes = $this->config->getPushes(); 48 | $routes = $this->config->getRoutes(); 49 | $remotes = $this->config->getRemotes(); 50 | $certs = $this->config->getCerts(); 51 | 52 | // If we have routes or pushes in lists then generate it 53 | if (count($pushes) || count($routes) || count($remotes)) { 54 | foreach ($pushes as $push) { 55 | $config[] = 'push "' . $push . '"'; 56 | } 57 | foreach ($routes as $route) { 58 | $config[] = 'route ' . $route; 59 | } 60 | foreach ($remotes as $remote) { 61 | $config[] = 'remote ' . $remote; 62 | } 63 | } 64 | 65 | // Certs should be below everything, due embedded keys and certificates 66 | if (count($certs) > 0) { 67 | foreach ($this->config->getCerts() as $key => $value) { 68 | $config[] .= isset($value['content']) 69 | ? "<$key>\n{$value['content']}\n" 70 | : "$key {$value['path']}"; 71 | } 72 | } 73 | 74 | return $config; 75 | } 76 | 77 | /** 78 | * Generate config in JSON format 79 | * 80 | * @return string 81 | */ 82 | private function generateJson(): string 83 | { 84 | $config = $this->generateArray(); 85 | 86 | return json_encode($config, JSON_PRETTY_PRINT); 87 | } 88 | 89 | /** 90 | * Generate config in RAW format 91 | * 92 | * @return string 93 | */ 94 | private function generateRaw(): string 95 | { 96 | $config = $this->generateArray(); 97 | 98 | return implode(PHP_EOL, $config); 99 | } 100 | 101 | /** 102 | * Generate config by parameters in memory 103 | * 104 | * @param string $type Type of generated config: raw (default), json, array 105 | * 106 | * @return array|string|null 107 | */ 108 | public function generate(string $type = 'raw') 109 | { 110 | if ($type === 'raw') { 111 | return $this->generateRaw(); 112 | } 113 | 114 | if ($type === 'json') { 115 | return $this->generateJson(); 116 | } 117 | 118 | if ($type === 'array') { 119 | return $this->generateArray(); 120 | } 121 | 122 | return null; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/Helpers.php: -------------------------------------------------------------------------------- 1 | load($filename); 28 | } elseif (null !== $filename) { 29 | $this->read($filename); 30 | } 31 | } 32 | 33 | /** 34 | * Check if line is valid config line, TRUE if line is okay. 35 | * If empty line or line with comment then FALSE. 36 | * 37 | * @param string $line 38 | * 39 | * @return bool 40 | */ 41 | private function isLine(string $line): bool 42 | { 43 | return !( 44 | // Empty lines 45 | preg_match('/^\n+|^[\t\s]*\n+/m', $line) || 46 | // Lines with comments 47 | preg_match('/^#/m', $line) 48 | ); 49 | } 50 | 51 | /** 52 | * {@inheritDoc} 53 | */ 54 | public function read(string $filename): array 55 | { 56 | $content = file_get_contents($filename); 57 | 58 | return $this->load($content); 59 | } 60 | 61 | /** 62 | * {@inheritDoc} 63 | */ 64 | public function load($content, string $type = 'raw'): array 65 | { 66 | $result = ['total' => 0, 'read' => 0]; 67 | $lines = []; 68 | 69 | if (is_array($content)) { 70 | $lines = $content; 71 | } elseif ($type === 'raw') { 72 | $lines = explode("\n", $content); 73 | } elseif ($type === 'json') { 74 | $lines = json_decode($content, false); 75 | } 76 | 77 | // Read line by line 78 | foreach ($lines as $line) { 79 | $line = trim($line); 80 | // Save line only of not empty 81 | if ($this->isLine($line) && strlen($line) > 1) { 82 | $line = trim(preg_replace('/\s+/', ' ', $line)); 83 | $this->lines[] = $line; 84 | $result['read']++; 85 | } 86 | $result['total']++; 87 | } 88 | 89 | return $result; 90 | } 91 | 92 | /** 93 | * {@inheritDoc} 94 | */ 95 | public function parse(): ConfigInterface 96 | { 97 | $config = new Config(); 98 | 99 | array_map( 100 | static function ($line) use ($config) { 101 | if (preg_match('/^(\S+)( (.*))?/', $line, $matches)) { 102 | $config->set($matches[1], $matches[3] ?? true); 103 | } 104 | }, 105 | $this->lines 106 | ); 107 | 108 | return $config; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Interfaces/ConfigInterface.php: -------------------------------------------------------------------------------- 1 | add('client')->add('remote', 'vpn.example.com'); 114 | */ 115 | public function set(string $name, $value = null): ConfigInterface; 116 | 117 | /** 118 | * Get some custom element 119 | * 120 | * @param string|null $name Name of parameter 121 | * 122 | * @return mixed 123 | */ 124 | public function get(string $name); 125 | 126 | /** 127 | * Set scope of certs 128 | * 129 | * @param \OpenVPN\Types\Cert[] $certs 130 | * @param bool $loadCertificates 131 | * 132 | * @return \OpenVPN\Interfaces\ConfigInterface 133 | */ 134 | public function setCerts(array $certs, bool $loadCertificates = false): ConfigInterface; 135 | 136 | /** 137 | * Set scope of unique pushes 138 | * 139 | * @param \OpenVPN\Types\Push[] $pushes 140 | * 141 | * @return \OpenVPN\Interfaces\ConfigInterface 142 | */ 143 | public function setPushes(array $pushes): ConfigInterface; 144 | 145 | /** 146 | * Set scope of unique routes 147 | * 148 | * @param \OpenVPN\Types\Route[] $routes 149 | * 150 | * @return \OpenVPN\Interfaces\ConfigInterface 151 | */ 152 | public function setRoutes(array $routes): ConfigInterface; 153 | 154 | /** 155 | * Set scope of unique remotes 156 | * 157 | * @param \OpenVPN\Types\Remote[] $remotes 158 | * 159 | * @return \OpenVPN\Interfaces\ConfigInterface 160 | */ 161 | public function setRemotes(array $remotes): ConfigInterface; 162 | 163 | /** 164 | * Set scope of unique parameters 165 | * 166 | * @param \OpenVPN\Types\Parameter[] $parameters 167 | * 168 | * @return \OpenVPN\Interfaces\ConfigInterface 169 | */ 170 | public function setParams(array $parameters): ConfigInterface; 171 | 172 | /** 173 | * Export array of all certificates 174 | * 175 | * @return array 176 | */ 177 | public function getCerts(): array; 178 | 179 | /** 180 | * Export array of all pushes 181 | * 182 | * @return array 183 | */ 184 | public function getPushes(): array; 185 | 186 | /** 187 | * Export array of all routes 188 | * 189 | * @return array 190 | */ 191 | public function getRoutes(): array; 192 | 193 | /** 194 | * Export array of all remotes 195 | * 196 | * @return array 197 | */ 198 | public function getRemotes(): array; 199 | 200 | /** 201 | * Export array of all parameters 202 | * 203 | * @return array 204 | */ 205 | public function getParameters(): array; 206 | } 207 | -------------------------------------------------------------------------------- /src/Interfaces/GeneratorInterface.php: -------------------------------------------------------------------------------- 1 | publishes([ 17 | __DIR__ . '/../../config/openvpn-client.php' => config_path('openvpn-client.php'), 18 | __DIR__ . '/../../config/openvpn-server.php' => config_path('openvpn-server.php'), 19 | ]); 20 | } 21 | 22 | /** 23 | * Register any application services. 24 | * 25 | * @return void 26 | */ 27 | public function register(): void 28 | { 29 | $this->mergeConfigFrom( 30 | __DIR__ . '/../../config/openvpn-client.php', 'openvpn-client' 31 | ); 32 | 33 | $this->mergeConfigFrom( 34 | __DIR__ . '/../../config/openvpn-server.php', 'openvpn-server' 35 | ); 36 | 37 | $this->app->bind(Wrapper::class); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Laravel/Wrapper.php: -------------------------------------------------------------------------------- 1 | client(); 28 | } 29 | 30 | /** 31 | * Get server configuration of OpenVPN 32 | * 33 | * @param array $params 34 | * 35 | * @return \OpenVPN\Interfaces\ConfigInterface 36 | */ 37 | public function server(array $params = []): ConfigInterface 38 | { 39 | $configs = config('openvpn-server'); 40 | $configs = array_merge($configs, $params); 41 | 42 | return new Config($configs); 43 | } 44 | 45 | /** 46 | * Get instance of config generator 47 | * 48 | * @param \OpenVPN\Interfaces\ConfigInterface $config 49 | * 50 | * @return \OpenVPN\Interfaces\GeneratorInterface 51 | */ 52 | public function generator(ConfigInterface $config): GeneratorInterface 53 | { 54 | return new Generator($config); 55 | } 56 | 57 | /** 58 | * Get instance of config importer 59 | * 60 | * @param string|null $filename 61 | * @param bool $isContent 62 | * 63 | * @return \OpenVPN\Interfaces\ImportInterface 64 | */ 65 | public function importer(string $filename = null, bool $isContent = false): ImportInterface 66 | { 67 | return new Import($filename, $isContent); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Types/Cert.php: -------------------------------------------------------------------------------- 1 | class = new Config(); 18 | } 19 | 20 | public function testClient(): void 21 | { 22 | $this->class->client(); 23 | $params = $this->class->getParameters(); 24 | self::assertArrayHasKey('client', $params); 25 | } 26 | 27 | public function testSetCert(): void 28 | { 29 | $this->class->setCert('ca', '/etc/openvpn/ca.crt', true); 30 | $this->class->setCert('cert', '/etc/openvpn/server.crt'); 31 | $this->class->setCert('key', '/etc/openvpn/server.key'); 32 | $this->class->setCert('dh', '/etc/openvpn/dh.pem'); 33 | $this->class->setCert('tls-auth', '/etc/openvpn/ta.key'); 34 | 35 | $certs = $this->class->getCerts(); 36 | self::assertCount(5, $certs); 37 | self::assertEquals('/etc/openvpn/ca.crt', $certs['ca']['content']); 38 | self::assertEquals('/etc/openvpn/server.crt', $certs['cert']['path']); 39 | } 40 | 41 | public function testUnsetCert(): void 42 | { 43 | $this->class->setCert('ca', '/etc/openvpn/ca.crt'); 44 | $this->class->setCert('cert', '/etc/openvpn/server.crt'); 45 | self::assertCount(2, $this->class->getCerts()); 46 | 47 | $this->class->unsetCert('ca'); 48 | self::assertCount(1, $this->class->getCerts()); 49 | 50 | self::assertTrue(isset($this->class->getCerts()['cert'])); 51 | } 52 | 53 | public function testGetCerts(): void 54 | { 55 | $this->class->setCert('ca', '/etc/openvpn/ca.crt'); 56 | $this->class->setCert('cert', '/etc/openvpn/server.crt'); 57 | $this->class->setCert('key', '/etc/openvpn/server.key'); 58 | $this->class->setCert('dh', '/etc/openvpn/dh.pem'); 59 | $this->class->setCert('tls-auth', '/etc/openvpn/ta.key'); 60 | $this->class->unsetCert('ca'); 61 | $this->class->unsetCert('dh'); 62 | 63 | self::assertCount(3, $this->class->getCerts()); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /tests/GeneratorTest.php: -------------------------------------------------------------------------------- 1 | 'vpn.example.com 1234', 20 | 'push' => 'dns 1.2.3.4', 21 | ]); 22 | $this->object = new Generator($config); 23 | } 24 | 25 | public function testGenerate(): void 26 | { 27 | $test1 = $this->object->generate('raw'); 28 | $test2 = $this->object->generate('array'); 29 | $test3 = $this->object->generate('json'); 30 | $test4 = $this->object->generate('awesome random string'); 31 | 32 | self::assertEquals("push \"dns 1.2.3.4\"\nremote vpn.example.com 1234", $test1); 33 | self::assertEquals(["push \"dns 1.2.3.4\"", "remote vpn.example.com 1234"], $test2); 34 | self::assertEquals("[\n \"push \\\"dns 1.2.3.4\\\"\",\n \"remote vpn.example.com 1234\"\n]", $test3); 35 | self::assertNull($test4); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/HelpersTest.php: -------------------------------------------------------------------------------- 1 | expectException(RuntimeException::class); 15 | Helpers::isCertAllowed('wrong'); 16 | } 17 | 18 | public function decamelizeDataProvider(): array 19 | { 20 | return [ 21 | ['input' => '', 'result' => ''], 22 | ['input' => 'MMM', 'result' => 'mmm'], 23 | ['input' => 'makeMeHappy', 'result' => 'make-me-happy'], 24 | ['input' => 'make-Me-Happy', 'result' => 'make-me-happy'], 25 | ['input' => 'make Me Happy', 'result' => 'make-me-happy'], 26 | ['input' => 'make me happy', 'result' => 'make-me-happy'], 27 | ]; 28 | } 29 | 30 | /** 31 | * @dataProvider decamelizeDataProvider 32 | * 33 | * @param string $input 34 | * @param string $result 35 | */ 36 | public function testDecamelize(string $input, string $result): void 37 | { 38 | $test = Helpers::decamelize($input); 39 | self::assertEquals($result, $test); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/ImportTest.php: -------------------------------------------------------------------------------- 1 | import = new Import(); 24 | $this->config = new Config(); 25 | 26 | // Set server options 27 | $this->config->dev = 'tun'; 28 | $this->config->proto = 'tcp'; 29 | $this->config->local = 'vpn.example.com'; 30 | $this->config->port = 1194; 31 | $this->config->resolvRetry = 'infinite'; 32 | $this->config->cipher = 'AES-256-CBC'; 33 | $this->config->redirectGateway = true; 34 | $this->config->server = '10.8.0.0 255.255.255.0'; 35 | $this->config->keepalive = '10 120'; 36 | $this->config->renegSec = 18000; 37 | $this->config->user = 'nobody'; 38 | $this->config->group = 'nogroup'; 39 | $this->config->persistKey = true; 40 | $this->config->persistTun = true; 41 | $this->config->compLzo = true; 42 | $this->config->verb = 3; 43 | $this->config->mute = 20; 44 | $this->config->status = '/var/log/openvpn/status.log'; 45 | $this->config->logAppend = '/var/log/openvpn/openvpn.log'; 46 | $this->config->clientConfigDir = 'ccd'; 47 | $this->config->scriptSecurity = 3; 48 | $this->config->usernameAsCommonName = true; 49 | $this->config->verifyClientCert = 'none'; 50 | $this->config->authUserPassVerify = 'your_script.sh via-file'; 51 | $this->config->duplicateCn = true; 52 | 53 | // Set routes which will be used by server after starting 54 | $this->config->setRoutes([ 55 | '10.1.1.0 255.255.255.0', 56 | '10.1.2.0 255.255.255.0', 57 | '10.1.3.0 255.255.255.0', 58 | '10.1.4.0 255.255.255.0', 59 | '10.1.5.0 255.255.255.0', 60 | ]); 61 | 62 | // Set additional certificates of server 63 | $this->config->setCerts([ 64 | 'ca' => '/etc/openvpn/keys/ca.crt', 65 | 'cert' => '/etc/openvpn/keys/issued/server.crt', 66 | ]); // You can embed certificates into config by adding true as second parameter of setCerts method 67 | 68 | // Another way for adding certificates 69 | $this->config 70 | ->setCert('key', '/etc/openvpn/keys/private/server.key') 71 | ->setCert('dh', '/etc/openvpn/keys/dh.pem'); 72 | 73 | // Set pushes which will be passed to client 74 | $this->config->setPushes([ 75 | // Additional routes, which clients will see 76 | 'route 10.1.2.0 255.255.255.0', 77 | 'route 10.1.3.0 255.255.255.0', 78 | 'route 10.1.4.0 255.255.255.0', 79 | 80 | // Replace default gateway, all client's traffic will be routed via VPN 81 | 'redirect-gateway def1', 82 | 83 | // Prepend additional DNS addresses 84 | 'dhcp-option DNS 8.8.8.8', 85 | 'dhcp-option DNS 8.8.4.4', 86 | ]); 87 | } 88 | 89 | public function testParse(): void 90 | { 91 | $this->import->lines = [ 92 | 'dev tun', 93 | 'proto tcp', 94 | 'local vpn.example.com', 95 | 'port 1194', 96 | 'resolv-retry infinite', 97 | 'cipher AES-256-CBC', 98 | 'redirect-gateway', 99 | 'server 10.8.0.0 255.255.255.0', 100 | 'keepalive 10 120', 101 | 'reneg-sec 18000', 102 | 'user nobody', 103 | 'group nogroup', 104 | 'persist-key', 105 | 'persist-tun', 106 | 'comp-lzo', 107 | 'verb 3', 108 | 'mute 20', 109 | 'status /var/log/openvpn/status.log', 110 | 'log-append /var/log/openvpn/openvpn.log', 111 | 'client-config-dir ccd', 112 | 'script-security 3', 113 | 'username-as-common-name', 114 | 'verify-client-cert none', 115 | 'auth-user-pass-verify your_script.sh via-file', 116 | 'duplicate-cn', 117 | 'route 10.1.1.0 255.255.255.0', 118 | 'route 10.1.2.0 255.255.255.0', 119 | 'route 10.1.3.0 255.255.255.0', 120 | 'route 10.1.4.0 255.255.255.0', 121 | 'route 10.1.5.0 255.255.255.0', 122 | 'push "route 10.1.2.0 255.255.255.0"', 123 | 'push "route 10.1.3.0 255.255.255.0"', 124 | 'push "route 10.1.4.0 255.255.255.0"', 125 | 'push "redirect-gateway def1"', 126 | 'push "dhcp-option DNS 8.8.8.8"', 127 | 'push "dhcp-option DNS 8.8.4.4"', 128 | 'ca /etc/openvpn/keys/ca.crt', 129 | 'cert /etc/openvpn/keys/issued/server.crt', 130 | 'key /etc/openvpn/keys/private/server.key', 131 | 'dh /etc/openvpn/keys/dh.pem', 132 | ]; 133 | 134 | $object = $this->import->parse(); 135 | 136 | self::assertInstanceOf(Config::class, $object); 137 | 138 | // For tests 139 | $pushes = $object->getPushes(); 140 | $certs = $object->getCerts(); 141 | $routes = $object->getRoutes(); 142 | $params = $object->getParameters(); 143 | 144 | self::assertCount(6, $pushes); 145 | self::assertCount(4, $certs); 146 | self::assertCount(5, $routes); 147 | self::assertCount(25, $params); 148 | } 149 | 150 | public function testRead(): void 151 | { 152 | $file = __DIR__ . '/server.ovpn'; 153 | $this->import->read($file); 154 | $object = $this->import->parse(); 155 | 156 | // For tests 157 | $pushes = $object->getPushes(); 158 | $certs = $object->getCerts(); 159 | $routes = $object->getRoutes(); 160 | $params = $object->getParameters(); 161 | 162 | self::assertCount(6, $pushes); 163 | self::assertCount(4, $certs); 164 | self::assertCount(5, $routes); 165 | self::assertCount(25, $params); 166 | } 167 | 168 | public function testLoad(): void 169 | { 170 | // Generate config 171 | $config = $this->config->generate(); 172 | 173 | // Load text via importer then parse and return object 174 | $this->import->load($config); 175 | $object = $this->import->parse(); 176 | 177 | // For tests 178 | $pushes = $object->getPushes(); 179 | $certs = $object->getCerts(); 180 | $routes = $object->getRoutes(); 181 | $params = $object->getParameters(); 182 | 183 | self::assertCount(6, $pushes); 184 | self::assertCount(4, $certs); 185 | self::assertCount(5, $routes); 186 | self::assertCount(25, $params); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /tests/sample.crt: -------------------------------------------------------------------------------- 1 | some crypto content 2 | -------------------------------------------------------------------------------- /tests/server.ovpn: -------------------------------------------------------------------------------- 1 | dev tun 2 | proto tcp 3 | local vpn.example.com 4 | port 1194 5 | resolv-retry infinite 6 | cipher AES-256-CBC 7 | redirect-gateway 8 | server 10.8.0.0 255.255.255.0 9 | keepalive 10 120 10 | reneg-sec 18000 11 | user nobody 12 | group nogroup 13 | persist-key 14 | persist-tun 15 | comp-lzo 16 | verb 3 17 | mute 20 18 | status /var/log/openvpn/status.log 19 | log-append /var/log/openvpn/openvpn.log 20 | client-config-dir ccd 21 | script-security 3 22 | username-as-common-name 23 | verify-client-cert none 24 | auth-user-pass-verify your_script.sh via-file 25 | duplicate-cn 26 | 27 | ### Networking 28 | route 10.1.1.0 255.255.255.0 29 | route 10.1.2.0 255.255.255.0 30 | route 10.1.3.0 255.255.255.0 31 | route 10.1.4.0 255.255.255.0 32 | route 10.1.5.0 255.255.255.0 33 | push "route 10.1.2.0 255.255.255.0" 34 | push "route 10.1.3.0 255.255.255.0" 35 | push "route 10.1.4.0 255.255.255.0" 36 | push "redirect-gateway def1" 37 | push "dhcp-option DNS 8.8.8.8" 38 | push "dhcp-option DNS 8.8.4.4" 39 | 40 | ### Certificates 41 | ca /etc/openvpn/keys/ca.crt 42 | cert /etc/openvpn/keys/issued/server.crt 43 | key /etc/openvpn/keys/private/server.key 44 | dh /etc/openvpn/keys/dh.pem 45 | --------------------------------------------------------------------------------