├── .gitignore ├── opn.go ├── .idea ├── encodings.xml ├── misc.xml ├── vcs.xml ├── sonarlint.xml ├── modules.xml ├── opnsense-cli.iml ├── dictionaries │ └── em.xml ├── php.xml └── runConfigurations │ ├── list.xml │ ├── status.xml │ ├── restart.xml │ ├── reconfigure.xml │ ├── delete.xml │ ├── show.xml │ ├── create.xml │ └── update.xml ├── cmd ├── unbound.go ├── unbound_service.go ├── unbound_service_restart.go ├── unbound_service_status.go ├── unbound_hostoverride.go ├── unbound_service_reconfigure.go ├── unbound_hostoverride_list.go ├── root.go ├── unbound_hostoverride_show.go ├── unbound_hostoverride_rm.go └── unbound_hostoverride_create_update.go ├── go.mod ├── Makefile ├── .github └── workflows │ └── build.yml ├── http └── opn_unbound.http ├── go.sum ├── LICENSE ├── opnsense └── api │ ├── api.go │ └── unbound │ ├── service.go │ └── hostoverride.go ├── Vagrantfile ├── README.md └── config.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/workspace.xml 2 | .env 3 | vendor 4 | #dist/ 5 | http/*.private.env.json 6 | dist/* 7 | -------------------------------------------------------------------------------- /opn.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/eugenmayer/opnsense-cli/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/sonarlint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /cmd/unbound.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var unboundCmd = &cobra.Command{ 8 | Use: "unbound", 9 | Short: "Manage Unbound using the OPNsense API", 10 | } 11 | 12 | func init() { 13 | RootCmd.AddCommand(unboundCmd) 14 | } 15 | -------------------------------------------------------------------------------- /cmd/unbound_service.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var unboundServiceCmd = &cobra.Command{ 8 | Use: "service", 9 | Short: "Manage Unbound service", 10 | } 11 | 12 | func init() { 13 | unboundCmd.AddCommand(unboundServiceCmd) 14 | } 15 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/eugenmayer/opnsense-cli 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/joho/godotenv v1.5.1 7 | github.com/spf13/cobra v1.8.0 8 | ) 9 | 10 | require ( 11 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 12 | github.com/spf13/pflag v1.0.5 // indirect 13 | ) 14 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/opnsense-cli.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/dictionaries/em.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | apisecret 5 | coreapi 6 | hostentry 7 | hostoverride 8 | mxprio 9 | nosslverify 10 | nsense 11 | openvpn 12 | 13 | 14 | -------------------------------------------------------------------------------- /.idea/php.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | init: 2 | go mod tidy 3 | go mod verify 4 | go mod vendor 5 | 6 | update: 7 | go get -u 8 | go mod tidy 9 | 10 | start: 11 | vagrant up opnsense 12 | docker-compose up -d 13 | 14 | rm: 15 | vagrant destroy 16 | docker-compose down -v 17 | 18 | build: init 19 | go build -o dist/opn-macos-amd64 opn.go 20 | env GOSS=linux go build -o dist/opn-linux-amd64 opn.go 21 | env GOSS=windows go build -o dist/opn-windows-amd64 opn.go 22 | release: 23 | go build -o dist/opn-macos-amd64-${VERSION} opn.go 24 | env GOSS=linux go build -o dist/opn-linux-amd64-${VERSION} opn.go 25 | -------------------------------------------------------------------------------- /.idea/runConfigurations/list.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/status.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/restart.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/reconfigure.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/delete.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/show.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/create.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations/update.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | - uses: actions/setup-go@v3 13 | with: 14 | go-version-file: 'go.mod' 15 | # push only on main 16 | - name: build 17 | run: make build 18 | - name: Release 19 | uses: softprops/action-gh-release@v1 20 | if: startsWith(github.ref, 'refs/tags/') 21 | with: 22 | files: | 23 | dist/opn-linux-amd64 24 | dist/opn-macos-amd64 25 | dist/opn-windows-amd64 26 | -------------------------------------------------------------------------------- /cmd/unbound_service_restart.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 5 | "github.com/spf13/cobra" 6 | "log" 7 | ) 8 | 9 | var unboundRestartCmd = &cobra.Command{ 10 | Use: "restart", 11 | Short: "Restart the unbound server", 12 | Run: unboundRestartRun, 13 | } 14 | 15 | func init() { 16 | unboundServiceCmd.AddCommand(unboundRestartCmd) 17 | } 18 | 19 | func unboundRestartRun(_ *cobra.Command, _ []string) { 20 | var unboundApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 21 | 22 | var err = unboundApi.ServiceRestart() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | log.Printf("Service restarted") 28 | } 29 | -------------------------------------------------------------------------------- /cmd/unbound_service_status.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 5 | "github.com/spf13/cobra" 6 | "log" 7 | ) 8 | 9 | var unboundStatusCmd = &cobra.Command{ 10 | Use: "status", 11 | Short: "Status of your unbound server", 12 | Run: unboundRStatusRun, 13 | } 14 | 15 | func init() { 16 | unboundServiceCmd.AddCommand(unboundStatusCmd) 17 | } 18 | 19 | func unboundRStatusRun(_ *cobra.Command, _ []string) { 20 | var unboundApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 21 | 22 | var status, err = unboundApi.ServiceStatus() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | log.Printf("Service status: %s", status) 28 | } 29 | -------------------------------------------------------------------------------- /cmd/unbound_hostoverride.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/spf13/cobra" 5 | ) 6 | 7 | var ( 8 | HOSTOVERRIDEhost string 9 | HOSTOVERRIDEdomain string 10 | HOSTOVERRIDEuuid string 11 | HOSTOVERRIDEip string 12 | HOSTOVERRIDErr string 13 | HOSTOVERRIDEmxprio string 14 | HOSTOVERRIDEmx string 15 | HOSTOVERRIDEdescription string 16 | // HOSTOVERRIDEnabled 0 for disabled, 1 for enabled 17 | HOSTOVERRIDEnabled string 18 | ) 19 | 20 | var unboundHostOverrideCmd = &cobra.Command{ 21 | Use: "hostoverride", 22 | Short: "Manage Unbound Host Override entries", 23 | } 24 | 25 | func init() { 26 | unboundCmd.AddCommand(unboundHostOverrideCmd) 27 | } 28 | -------------------------------------------------------------------------------- /cmd/unbound_service_reconfigure.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 5 | "github.com/spf13/cobra" 6 | "log" 7 | ) 8 | 9 | var unboundReconfigureCmd = &cobra.Command{ 10 | Use: "reconfigure", 11 | Short: "Reconfigure the unbound server", 12 | Run: unboundReconfigureRun, 13 | } 14 | 15 | func init() { 16 | unboundServiceCmd.AddCommand(unboundReconfigureCmd) 17 | } 18 | 19 | func unboundReconfigureRun(_ *cobra.Command, _ []string) { 20 | var unboundApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 21 | 22 | var err = unboundApi.ServiceReconfigure() 23 | if err != nil { 24 | log.Fatal(err) 25 | } 26 | 27 | log.Printf("Service reconfigured") 28 | } 29 | -------------------------------------------------------------------------------- /cmd/unbound_hostoverride_list.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 6 | "github.com/spf13/cobra" 7 | "log" 8 | ) 9 | 10 | var unboundHostOverrideListCmd = &cobra.Command{ 11 | Use: "list", 12 | Short: "List all Unbound Host Override entries", 13 | Run: hostEntryListRun, 14 | } 15 | 16 | func init() { 17 | unboundHostOverrideCmd.AddCommand(unboundHostOverrideListCmd) 18 | } 19 | 20 | func hostEntryListRun(_ *cobra.Command, _ []string) { 21 | var unboundApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 22 | 23 | var hostOverrideEntries, err = unboundApi.HostOverrideList() 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | for _, hostEntry := range hostOverrideEntries { 28 | fmt.Println(hostEntry) 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | opnsenseapi "github.com/eugenmayer/opnsense-cli/opnsense/api" 6 | "github.com/spf13/cobra" 7 | "log" 8 | "os" 9 | ) 10 | 11 | var RootCmd = &cobra.Command{ 12 | Use: "opnsense", 13 | Short: "OPNsense cli to operate with a opnsense API", 14 | } 15 | 16 | // Execute adds all child commands to the root command and sets flags appropriately. 17 | // This is called by main.main(). It only needs to happen once to the RootCmd. 18 | func Execute() { 19 | if err := RootCmd.Execute(); err != nil { 20 | fmt.Println(err) 21 | os.Exit(1) 22 | } 23 | } 24 | 25 | func init() { 26 | RootCmd.PersistentFlags().BoolP("verbose", "v", false, "Activate for verbose") 27 | } 28 | 29 | func GetOPNsenseApi() *opnsenseapi.OPNsense { 30 | connection, err := opnsenseapi.ConfigureFromEnv() 31 | if err != nil { 32 | log.Fatal() 33 | } 34 | return connection 35 | } 36 | 37 | func BoolToInt(b bool) int { 38 | if b { 39 | return 1 40 | } 41 | return 0 42 | } 43 | -------------------------------------------------------------------------------- /http/opn_unbound.http: -------------------------------------------------------------------------------- 1 | ### 2 | ### Add host override 3 | ### 4 | POST {{baseUrl}}/api/unbound/settings/addHostOverride 5 | Authorization: Basic {{username}} {{password}} 6 | Content-Type: application/json 7 | Accept: application/json 8 | 9 | { 10 | "host" : { 11 | "enabled": "1", 12 | "hostname": "test", 13 | "domain": "fest.local", 14 | "rr": "A", 15 | "server": "1.1.1.1" 16 | } 17 | } 18 | 19 | ### 20 | ### Delete host override 21 | ### 22 | POST {{baseUrl}}/api/unbound/settings/delHostOverride/84364c29-dce8-4d81-9681-a7e789e8c035 23 | Authorization: Basic {{username}} {{password}} 24 | Accept: application/json 25 | 26 | 27 | ### 28 | ### Search host override 29 | ### 30 | POST {{baseUrl}}/api/unbound/settings/searchHostOverride 31 | Authorization: Basic {{username}} {{password}} 32 | Accept: application/json 33 | 34 | 35 | ### 36 | ### Search host override with search phrase 37 | ### 38 | POST {{baseUrl}}/api/unbound/settings/searchHostOverride?searchPhrase=test.local 39 | Authorization: Basic {{username}} {{password}} 40 | Accept: application/json 41 | -------------------------------------------------------------------------------- /cmd/unbound_hostoverride_show.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 6 | "github.com/spf13/cobra" 7 | "log" 8 | ) 9 | 10 | var unboundHostOverrideShowCmd = &cobra.Command{ 11 | Use: "show", 12 | Short: "Show a Unbound Host override", 13 | Run: hostOverrideShowRun, 14 | } 15 | 16 | func init() { 17 | unboundHostOverrideShowCmd.Flags().StringVarP(&HOSTOVERRIDEhost, "host", "", "", "the host part") 18 | unboundHostOverrideShowCmd.Flags().StringVarP(&HOSTOVERRIDEdomain, "domain", "", "", "the domain part") 19 | 20 | _ = unboundHostOverrideShowCmd.MarkFlagRequired("host") 21 | _ = unboundHostOverrideShowCmd.MarkFlagRequired("domain") 22 | 23 | unboundHostOverrideCmd.AddCommand(unboundHostOverrideShowCmd) 24 | } 25 | 26 | func hostOverrideShowRun(_ *cobra.Command, _ []string) { 27 | var openvpnApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 28 | 29 | var ccd, err = openvpnApi.HostEntryGetByFQDN(HOSTOVERRIDEhost, HOSTOVERRIDEdomain) 30 | if err != nil { 31 | log.Fatal(err) 32 | } 33 | fmt.Println(ccd) 34 | } 35 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 2 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 3 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 4 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 5 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 6 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 7 | github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= 8 | github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= 9 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 10 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 11 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 12 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Eugen Mayer 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 | -------------------------------------------------------------------------------- /cmd/unbound_hostoverride_rm.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 6 | "github.com/spf13/cobra" 7 | "log" 8 | ) 9 | 10 | var unboundHostOverrideRmCmd = &cobra.Command{ 11 | Use: "rm", 12 | Short: "Remove a Unbound host override entry", 13 | Run: hostEntryRmRun, 14 | } 15 | 16 | func init() { 17 | unboundHostOverrideRmCmd.Flags().StringVarP(&HOSTOVERRIDEhost, "host", "", "", "lookup entry by the host / domain the host part") 18 | unboundHostOverrideRmCmd.Flags().StringVarP(&HOSTOVERRIDEdomain, "domain", "", "", "lookup entry by the host / domainthe domain part") 19 | unboundHostOverrideRmCmd.Flags().StringVarP(&HOSTOVERRIDEuuid, "uuid", "", "", "delete by uuid explicitly") 20 | 21 | unboundHostOverrideCmd.AddCommand(unboundHostOverrideRmCmd) 22 | } 23 | 24 | func hostEntryRmRun(_ *cobra.Command, _ []string) { 25 | var openvpnApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 26 | 27 | if HOSTOVERRIDEhost == "" || HOSTOVERRIDEdomain == "" && HOSTOVERRIDEuuid == "" { 28 | log.Fatal("Please either set host and domain or set uuid") 29 | } 30 | 31 | if HOSTOVERRIDEhost != "" && HOSTOVERRIDEdomain != "" { 32 | var entry, err = openvpnApi.HostEntryGetByFQDN(HOSTOVERRIDEhost, HOSTOVERRIDEdomain) 33 | if err != nil { 34 | log.Fatal(err.Error()) 35 | } 36 | HOSTOVERRIDEuuid = entry.Uuid 37 | log.Printf("Found Uuid %s for FQDM %s.%s", HOSTOVERRIDEuuid, HOSTOVERRIDEhost, HOSTOVERRIDEdomain) 38 | } 39 | 40 | var err = openvpnApi.HostEntryRemove(HOSTOVERRIDEuuid) 41 | if err != nil { 42 | log.Fatal(err) 43 | } 44 | fmt.Println("deleted successfully") 45 | } 46 | -------------------------------------------------------------------------------- /cmd/unbound_hostoverride_create_update.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 6 | "github.com/spf13/cobra" 7 | "log" 8 | "net" 9 | ) 10 | 11 | var unboundHostOverrideCreateCmd = &cobra.Command{ 12 | Use: "create", 13 | Short: "Create an Unbound Host Override", 14 | Run: hostOverrideCreateUpdateRun, 15 | } 16 | 17 | var unboundHostOverrideUpdateCmd = &cobra.Command{ 18 | Use: "update", 19 | Short: "Update an Unbound Host Override", 20 | Run: hostOverrideCreateUpdateRun, 21 | } 22 | 23 | func init() { 24 | setupUnboundHostOverrideCreateUpdateCommand(unboundHostOverrideCreateCmd) 25 | setupUnboundHostOverrideCreateUpdateCommand(unboundHostOverrideUpdateCmd) 26 | unboundHostOverrideCmd.AddCommand(unboundHostOverrideCreateCmd) 27 | unboundHostOverrideCmd.AddCommand(unboundHostOverrideUpdateCmd) 28 | } 29 | 30 | func setupUnboundHostOverrideCreateUpdateCommand(command *cobra.Command) { 31 | command.Flags().StringVarP(&HOSTOVERRIDEhost, "host", "", "", "the host part") 32 | command.Flags().StringVarP(&HOSTOVERRIDEdomain, "domain", "", "", "the domain part") 33 | command.Flags().StringVarP(&HOSTOVERRIDEip, "ip", "", "", "the ip4 address like 10.10.10.10") 34 | command.Flags().StringVarP(&HOSTOVERRIDErr, "rr", "", "A", "Record type, defaults to A") 35 | command.Flags().StringVarP(&HOSTOVERRIDEmxprio, "mxprio", "", "", "MX Prio a number, defaults to empty") 36 | command.Flags().StringVarP(&HOSTOVERRIDEmx, "mx", "", "", "MX Host, defaults to empty") 37 | command.Flags().StringVarP(&HOSTOVERRIDEdescription, "description", "", "", "Entry description") 38 | command.Flags().StringVarP(&HOSTOVERRIDEnabled, "enabled", "", "1", "Should the entry be enabled. 1 (true) by default") 39 | 40 | _ = command.MarkFlagRequired("host") 41 | _ = command.MarkFlagRequired("domain") 42 | _ = command.MarkFlagRequired("ip") 43 | } 44 | 45 | func hostOverrideCreateUpdateRun(_ *cobra.Command, _ []string) { 46 | var unboundApi = unbound.UnboundApi{OPNsense: GetOPNsenseApi()} 47 | hostOverride := unbound.HostOverride{ 48 | Host: HOSTOVERRIDEhost, 49 | Domain: HOSTOVERRIDEdomain, 50 | Ip: HOSTOVERRIDEip, 51 | Mxprio: HOSTOVERRIDEmxprio, 52 | Mx: HOSTOVERRIDEmx, 53 | Description: HOSTOVERRIDEdescription, 54 | Rr: HOSTOVERRIDErr, 55 | Enabled: HOSTOVERRIDEnabled, 56 | } 57 | 58 | if net.ParseIP(hostOverride.Ip) == nil { 59 | log.Fatal(fmt.Sprintf("your IP is invalid: %s", hostOverride.Ip)) 60 | } 61 | 62 | var uuid, err = unboundApi.HostOverrideCreateOrUpdate(hostOverride) 63 | if err != nil { 64 | log.Fatal(err) 65 | } 66 | fmt.Println(fmt.Sprintf("Create or update successfully, uuid: %s", uuid)) 67 | } 68 | -------------------------------------------------------------------------------- /opnsense/api/api.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "crypto/tls" 5 | "crypto/x509" 6 | "errors" 7 | "fmt" 8 | "github.com/joho/godotenv" 9 | "net/http" 10 | "net/url" 11 | "os" 12 | ) 13 | 14 | type OPNsense struct { 15 | BaseUrl url.URL 16 | ApiKey string 17 | ApiSecret string 18 | NoSslVerify bool 19 | } 20 | 21 | func (opn *OPNsense) Send(request *http.Request) (*http.Response, error) { 22 | var client = &http.Client{} 23 | 24 | certPool, _ := x509.SystemCertPool() 25 | client.Transport = &http.Transport{ 26 | TLSClientConfig: &tls.Config{ 27 | InsecureSkipVerify: opn.NoSslVerify, 28 | RootCAs: certPool, 29 | }, 30 | } 31 | 32 | request.SetBasicAuth(opn.ApiKey, opn.ApiSecret) 33 | return client.Do(request) 34 | } 35 | 36 | type NotFoundError struct { 37 | Name string 38 | Err error 39 | } 40 | 41 | type TooManyFoundError struct { 42 | Name string 43 | Err error 44 | } 45 | 46 | func (f *NotFoundError) Error() string { 47 | return fmt.Sprintf("not found: %s", f.Name) 48 | } 49 | 50 | func (f *TooManyFoundError) Error() string { 51 | return fmt.Sprintf("too many found: %s", f.Name) 52 | } 53 | 54 | func ConfigureFromEnv() (*OPNsense, error) { 55 | _ = godotenv.Load() 56 | 57 | if _, isset := os.LookupEnv("OPN_URL"); !isset { 58 | return nil, errors.New(fmt.Sprintf("Please set the OPN_URL to your opnsense opnUrl like https://myopnsense:10443")) 59 | } 60 | 61 | if _, isset := os.LookupEnv("OPN_APIKEY"); !isset { 62 | return nil, errors.New(fmt.Sprintf("Please set OPN_APIKEY to your opnsense api apiKey")) 63 | } 64 | 65 | if _, isset := os.LookupEnv("OPN_APISECRET"); !isset { 66 | return nil, errors.New(fmt.Sprintf("Please set OPN_APISECRET to your opnsense api apiSecret")) 67 | } 68 | 69 | var parsedUrl, err = url.Parse(os.Getenv("OPN_URL")) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | return &OPNsense{ 75 | BaseUrl: *parsedUrl, 76 | ApiKey: os.Getenv("OPN_APIKEY"), 77 | ApiSecret: os.Getenv("OPN_APISECRET"), 78 | NoSslVerify: os.Getenv("OPN_NOSSLVERIFY") == "1", 79 | }, nil 80 | } 81 | 82 | // EndpointForModule so basically api/ 83 | func (opn *OPNsense) EndpointForModule(module string) string { 84 | return fmt.Sprintf("%s/api/%s", opn.BaseUrl.String(), module) 85 | } 86 | 87 | // EndpointForModuleController so basically api// 88 | func (opn *OPNsense) EndpointForModuleController(module string, controller string) string { 89 | return fmt.Sprintf("%s/%s", opn.EndpointForModule(module), controller) 90 | } 91 | 92 | // EndpointForPluginControllerMethod so basically api/// 93 | func (opn *OPNsense) EndpointForPluginControllerMethod(module string, controller string, method string) string { 94 | return fmt.Sprintf("%s/%s", opn.EndpointForModuleController(module, controller), method) 95 | } 96 | -------------------------------------------------------------------------------- /opnsense/api/unbound/service.go: -------------------------------------------------------------------------------- 1 | package unbound 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "net/http" 8 | ) 9 | 10 | func (opn *UnboundApi) ServiceRestart() error { 11 | // endpoint 12 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "service", "restart") 13 | 14 | // create our Request 15 | request, reqCreationErr := http.NewRequest("POST", endpoint, nil) 16 | if reqCreationErr != nil { 17 | return reqCreationErr 18 | } 19 | 20 | var response, reqErr = opn.Send(request) 21 | if reqErr != nil { 22 | return reqErr 23 | } 24 | 25 | if response.StatusCode == 200 { 26 | // else 27 | return nil 28 | } else { 29 | var container struct { 30 | Status string `json:"status"` 31 | Message string `json:"message"` 32 | } 33 | 34 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 35 | return err 36 | } 37 | return errors.New(fmt.Sprintf("%s:%s", container.Message, reqErr)) 38 | } 39 | } 40 | 41 | func (opn *UnboundApi) ServiceReconfigure() error { 42 | // endpoint 43 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "service", "reconfigure") 44 | 45 | // create our Request 46 | request, reqCreationErr := http.NewRequest("POST", endpoint, nil) 47 | if reqCreationErr != nil { 48 | return reqCreationErr 49 | } 50 | 51 | var response, reqErr = opn.Send(request) 52 | if reqErr != nil { 53 | return reqErr 54 | } 55 | 56 | if response.StatusCode == 200 { 57 | // else 58 | return nil 59 | } else { 60 | var container struct { 61 | Status string `json:"status"` 62 | Message string `json:"message"` 63 | } 64 | 65 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 66 | return err 67 | } 68 | return errors.New(fmt.Sprintf("%s:%s", container.Message, reqErr)) 69 | } 70 | } 71 | 72 | func (opn *UnboundApi) ServiceStatus() (string, error) { 73 | // endpoint 74 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "service", "status") 75 | 76 | // create our Request 77 | request, reqCreationErr := http.NewRequest("GET", endpoint, nil) 78 | if reqCreationErr != nil { 79 | return "", reqCreationErr 80 | } 81 | 82 | var response, reqErr = opn.Send(request) 83 | if reqErr != nil { 84 | return "", reqErr 85 | } 86 | 87 | if response.StatusCode == 200 { 88 | var container struct { 89 | Status string `json:"status"` 90 | } 91 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 92 | return "", err 93 | } 94 | // else 95 | return container.Status, nil 96 | } else { 97 | var container struct { 98 | Status string `json:"status"` 99 | Message string `json:"message"` 100 | } 101 | 102 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 103 | return "", err 104 | } 105 | return "", errors.New(fmt.Sprintf("%s:%s", container.Message, reqErr)) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | module LocalCommand 2 | class Config < Vagrant.plugin("2", :config) 3 | attr_accessor :command 4 | end 5 | 6 | class Plugin < Vagrant.plugin("2") 7 | name "local_shell" 8 | 9 | config(:local_shell, :provisioner) do 10 | Config 11 | end 12 | 13 | provisioner(:local_shell) do 14 | Provisioner 15 | end 16 | end 17 | 18 | class Provisioner < Vagrant.plugin("2", :provisioner) 19 | def provision 20 | result = system "#{config.command}" 21 | end 22 | end 23 | end 24 | 25 | Vagrant.configure("2") do |config| 26 | config.vm.box = "eugenmayer/opnsense" 27 | 28 | config.ssh.sudo_command = "%c" 29 | config.ssh.shell = "/bin/sh" 30 | config.ssh.password = "opnsense" 31 | config.ssh.username = "root" 32 | config.ssh.port = "10022" 33 | # we need to use rsync, no vbox drivers for bsd 34 | config.vm.synced_folder ".", "/vagrant", disabled: true 35 | 36 | config.vm.define 'opnsense', autostart: false do |test| 37 | test.vm.provider 'virtualbox' do |vb| 38 | vb.customize ['modifyvm',:id, '--nic1', 'intnet', '--nic2', 'nat'] # swap the networks around 39 | vb.customize ['modifyvm', :id, '--natpf2', "ssh,tcp,127.0.0.1,10022,,22" ] #port forward 40 | vb.customize ['modifyvm', :id, '--natpf2', "https,tcp,127.0.0.1,10443,,443" ] #port forward 41 | vb.customize ['modifyvm', :id, '--natpf2', "openvpn,tcp,127.0.0.1,11194,,1194" ] # openvpn 42 | #vb.customize ['modifyvm', :id, '--natpf1', "https,tcp,127.0.0.1,1443,,443" ] #port forward 43 | end 44 | 45 | # install dev tools 46 | test.vm.provision "shell", 47 | inline: "pkg update && pkg install -y vim-lite joe nano gnu-watch git tmux screen", 48 | run: "once" 49 | 50 | # replace the public ssh key for the root user with the one vagrant deployed for comms before we restart - or we lock vagrant out 51 | test.vm.provision "inject-pubkey-into-config", type: "local_shell", command: "export PUB=$(ssh-keygen -f .vagrant/machines/opnsense/virtualbox/private_key -y | base64) && xmlstarlet ed --inplace -u '/opnsense/system/user/authorizedkeys' -v \"$PUB\" config.xml" 52 | # apply our configuration so we have a configured radius with users and clients and an active openvpn server 53 | test.vm.provision "file", source: "./config.xml", destination: "/conf/config.xml" 54 | test.vm.provision "shell", 55 | inline: "echo 'rebooting to apply config' && reboot" 56 | 57 | test.vm.provision "sleep-for-reboot", type: "local_shell", command: "echo 'waiting for the reboot' && sleep 50" 58 | 59 | test.vm.provision "shell", 60 | inline: "setenv openvpn_version 0.0.4 && curl -Lo os-openvpn-devel-${openvpn_version}.txz https://github.com/EugenMayer/opnsense-openvpn-plugin/raw/master/dist/os-openvpn-devel-${openvpn_version}.txz && pkg add os-openvpn-devel-${openvpn_version}.txz" 61 | test.vm.provision "shell", 62 | inline: "setenv unbound_version 0.0.2 && curl -Lo os-unbound-devel-${unbound_version}.txz https://github.com/EugenMayer/opnsense-unbound-plugin/raw/master/dist/os-unbound-devel-${unbound_version}.txz && pkg add os-unbound-devel-${unbound_version}.txz" 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![build](https://github.com/EugenMayer/opnsense-cli/actions/workflows/build.yml/badge.svg)](https://github.com/EugenMayer/opnsense-cli/actions/workflows/build.yml) 2 | 3 | ## WAT 4 | 5 | Implementation of the OPNsense WebAPI to be used on the CLI or as a library - written in Golang 6 | 7 | Compatible with OPNsense 22.1 and higher. 8 | 9 | ## Installation 10 | 11 | Its precompiled and has no dependencies, so just download the binary, and you are good to go 12 | 13 | Pick a release from https://github.com/EugenMayer/opnsense-cli/releases 14 | chmod +x opn-* 15 | 16 | You need to create a .env (dotenv) for the secrets, or expose them into your ENV using `export` 17 | 18 | OPN_URL=https://localhost:10443 19 | OPN_APIKEY=5GWbPwKfXVLzgJnewKuu1IPw2HS7s510jKHmTM+rLA1y9VfEFE57yj/kJiWbXREB0EgpBK48u4gnyign 20 | OPN_APISECRET=EtpPVbiCBdtvG5VDlYJQfLu7Qck2hRffoLi2vb73arn5bKzxEbGdti8+iZetgc9eHABJy6XYG6/UsW/1` 21 | # if we should not verify SSL while talking to opn, enable that 22 | #OPN_NOSSLVERIFY=1 23 | 24 | ## Commands included 25 | 26 | Just run 27 | 28 | opn 29 | 30 | to see a full list. Currently implemented 31 | 32 | - managing host overrides for unbound (CRUD + list) 33 | 34 | 35 | ## Usage: cli 36 | 37 | opn --help 38 | 39 | # unbound host DNS entries 40 | opn unbound hostoverride create --host foo --domain bar.tld --ip 10.10.10.1 41 | opn unbound hostoverride update --host foo --domain bar.tld --ip 10.10.10.2 42 | opn unbound hostoverride show --host foo --domain bar.tld 43 | opn unbound hostoverride rm --host foo --domain bar.tld 44 | opn unbound hostoverride list 45 | 46 | # unbound service 47 | opn unbound service restart 48 | opn unbound service reconfigure 49 | opn unbound service status 50 | 51 | **HINT**: Right now, as of 22.7, you have to run `reconfigure` everytime you change a hostentry on `unbound`, so e.g. 52 | 53 | # this will yet not show up when you run a DNS query 54 | opn unbound hostoverride create --host foo --domain bar.tld --ip 10.10.10.1 55 | # after that, it will 56 | opn unbound service reconfigure 57 | 58 | This might change in later releases. 59 | 60 | ## Usage: GoLang library 61 | 62 | import ( 63 | opn_unbound "github.com/eugenmayer/opnsense-cli/opnsense/api/unbound" 64 | opn_api "github.com/eugenmayer/opnsense-cli/opnsense/api" 65 | ) 66 | 67 | func create_host_entry() error { 68 | var opnUnboundConnection opn_unbound.UnboundApi 69 | 70 | if opnConnection, opnErr := opn_api.ConfiugreFromEnv(); opnErr != nil { 71 | return errors.New(fmt.Sprintf("Error getting OPNsense connection: %s", opnErr)) 72 | } else { 73 | opnUnboundConnection = opn_unbound.UnboundApi{opnConnection} 74 | } 75 | 76 | var dnsHostEntry = opn_unbound.HostOverride{ 77 | Host: "test, 78 | Domain: "foo.tld, 79 | Ip: "10.10.10.1", 80 | } 81 | 82 | _, _ := opnUnboundConnection.HostOverrideCreateOrUpdate(dnsHostEntry) 83 | } 84 | 85 | ## Development 86 | 87 | # building 88 | make build 89 | 90 | ## Contributions 91 | 92 | If you like to add a new command implementing OPNsense API reference https://docs.opnsense.org/development/api.html#introduction - open a PR and iam happy to add it. Be bold. 93 | -------------------------------------------------------------------------------- /opnsense/api/unbound/hostoverride.go: -------------------------------------------------------------------------------- 1 | package unbound 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | coreapi "github.com/eugenmayer/opnsense-cli/opnsense/api" 9 | "io" 10 | "net/http" 11 | ) 12 | 13 | //goland:noinspection GoNameStartsWithPackageName 14 | type UnboundApi struct { 15 | *coreapi.OPNsense 16 | } 17 | 18 | type HostOverride struct { 19 | // 0 for disabled, 1 for enabled 20 | Enabled string `json:"enabled"` 21 | Host string `json:"hostname"` 22 | Domain string `json:"domain"` 23 | Ip string `json:"server"` 24 | Rr string `json:"rr"` // A, MX, CNAME... 25 | Mxprio string `json:"mxprio"` // 10, 20 26 | Mx string `json:"mx"` // mail.domain.tld ... 27 | Description string `json:"description"` // any arbitrary text 28 | Uuid string `json:"uuid"` 29 | } 30 | 31 | func (opn *UnboundApi) HostOverrideCreateOrUpdate(hostOverride HostOverride) (string, error) { 32 | if hostOverride.Uuid == "" { // no uuid given use host / domain based fuzzy search 33 | var searchResult, _ = opn.HostEntryGetByFQDN(hostOverride.Host, hostOverride.Domain) 34 | 35 | if searchResult.Uuid != "" { 36 | fmt.Println(fmt.Sprintf("Found entry with same FQDN, doing update with uuid: %s", searchResult.Uuid)) 37 | hostOverride.Uuid = searchResult.Uuid 38 | } 39 | } 40 | 41 | if hostOverride.Uuid == "" { 42 | return opn.HostOverrideCreate(hostOverride) 43 | } else { 44 | return opn.HostOverrideUpdate(hostOverride) 45 | } 46 | } 47 | 48 | func (opn *UnboundApi) HostOverrideUpdate(hostOverride HostOverride) (string, error) { 49 | // endpoint 50 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "settings", "setHostOverride") 51 | var fullPath = fmt.Sprintf("%s/%s", endpoint, hostOverride.Uuid) 52 | 53 | var container struct { 54 | HostOverride HostOverride `json:"host"` 55 | } 56 | 57 | container.HostOverride = hostOverride 58 | // create our Request 59 | jsonBody := new(bytes.Buffer) 60 | if err := json.NewEncoder(jsonBody).Encode(container); err != nil { 61 | return "", err 62 | } 63 | 64 | request, reqCreationErr := http.NewRequest("POST", fullPath, jsonBody) 65 | if reqCreationErr != nil { 66 | return "", reqCreationErr 67 | } 68 | request.Header.Set("Content-Type", "application/json") 69 | 70 | var response, reqErr = opn.Send(request) 71 | if reqErr != nil { 72 | return "", reqErr 73 | } 74 | 75 | if response.StatusCode == 200 { 76 | var resultContainer struct { 77 | ResultStatus string `json:"result"` 78 | Uuid string `json:"uuid"` 79 | } 80 | 81 | if err := json.NewDecoder(response.Body).Decode(&resultContainer); err != nil { 82 | return "", err 83 | } 84 | // else 85 | return resultContainer.Uuid, nil 86 | } else { 87 | var container struct { 88 | Status string `json:"status"` 89 | Message string `json:"message"` 90 | } 91 | 92 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 93 | return "", err 94 | } 95 | 96 | return "", errors.New(container.Message) 97 | } 98 | } 99 | 100 | func (opn *UnboundApi) HostOverrideCreate(hostOverride HostOverride) (string, error) { 101 | // endpoint 102 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "settings", "addHostOverride") 103 | 104 | var container struct { 105 | HostOverride HostOverride `json:"host"` 106 | } 107 | 108 | container.HostOverride = hostOverride 109 | // create our Request 110 | jsonBody := new(bytes.Buffer) 111 | if err := json.NewEncoder(jsonBody).Encode(container); err != nil { 112 | return "", err 113 | } 114 | 115 | request, reqCreationErr := http.NewRequest("POST", endpoint, jsonBody) 116 | if reqCreationErr != nil { 117 | return "", reqCreationErr 118 | } 119 | request.Header.Set("Content-Type", "application/json") 120 | 121 | var response, reqErr = opn.Send(request) 122 | if reqErr != nil { 123 | return "", reqErr 124 | } 125 | 126 | if response.StatusCode == 200 { 127 | var resultContainer struct { 128 | ResultStatus string `json:"result"` 129 | Uuid string `json:"uuid"` 130 | } 131 | 132 | if err := json.NewDecoder(response.Body).Decode(&resultContainer); err != nil { 133 | return "", err 134 | } 135 | // else 136 | return resultContainer.Uuid, nil 137 | } else { 138 | var container struct { 139 | Status string `json:"status"` 140 | Message string `json:"message"` 141 | } 142 | 143 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 144 | return "", err 145 | } 146 | 147 | return "", errors.New(container.Message) 148 | } 149 | } 150 | 151 | func (opn *UnboundApi) HostEntryGetByFQDN(host string, domain string) (HostOverride, error) { 152 | // endpoint 153 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "settings", "searchHostOverride") 154 | 155 | // we search for the host first and compare the domain later (searchPhrase does not support searching for FQDN) 156 | var reqUrl = fmt.Sprintf("%s?searchPhrase=%s", endpoint, host) 157 | // create our Request 158 | var request, reqCreationErr = http.NewRequest("GET", reqUrl, nil) 159 | 160 | if reqCreationErr != nil { 161 | return HostOverride{}, reqCreationErr 162 | } 163 | 164 | // send it to the server 165 | var response, reqErr = opn.Send(request) 166 | if reqErr != nil { 167 | bodyBytes, _ := io.ReadAll(response.Body) 168 | bodyString := string(bodyBytes) 169 | return HostOverride{}, errors.New(fmt.Sprintf("%s:%s", bodyString, reqErr)) 170 | } 171 | 172 | if response.StatusCode == 200 { 173 | var container struct { 174 | Overrides []HostOverride `json:"rows"` 175 | } 176 | 177 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 178 | return HostOverride{}, err 179 | } 180 | // else 181 | 182 | var allWithMatchingDomain = Filter(container.Overrides, func(override HostOverride) bool { 183 | return override.Domain == domain 184 | }) 185 | 186 | if len(allWithMatchingDomain) > 1 { 187 | return HostOverride{}, &coreapi.TooManyFoundError{ 188 | Err: nil, 189 | Name: "found more then one entry", 190 | } 191 | } 192 | 193 | if len(allWithMatchingDomain) == 0 { 194 | return HostOverride{}, &coreapi.NotFoundError{ 195 | Err: nil, 196 | Name: "hostentry", 197 | } 198 | } 199 | 200 | // else 201 | return allWithMatchingDomain[0], nil 202 | } else if response.StatusCode == 404 { 203 | return HostOverride{}, &coreapi.NotFoundError{ 204 | Err: nil, 205 | Name: "hostentry", 206 | } 207 | } else { 208 | var container struct { 209 | Status string `json:"status"` 210 | Message string `json:"message"` 211 | } 212 | 213 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 214 | return HostOverride{}, err 215 | } 216 | return HostOverride{}, errors.New(fmt.Sprintf("%s", container.Message)) 217 | } 218 | } 219 | 220 | func (opn *UnboundApi) HostOverrideList() ([]HostOverride, error) { 221 | // endpoint 222 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "settings", "searchHostOverride") 223 | 224 | // create our Request 225 | var request, reqCreationErr = http.NewRequest("GET", endpoint, nil) 226 | 227 | if reqCreationErr != nil { 228 | return []HostOverride{}, reqCreationErr 229 | } 230 | 231 | // send it to the server 232 | var response, reqErr = opn.Send(request) 233 | if reqErr != nil { 234 | bodyBytes, _ := io.ReadAll(response.Body) 235 | bodyString := string(bodyBytes) 236 | return []HostOverride{}, errors.New(fmt.Sprintf("%s:%s", bodyString, reqErr)) 237 | } 238 | 239 | if response.StatusCode == 200 { 240 | var container struct { 241 | Overrides []HostOverride `json:"rows"` 242 | } 243 | 244 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 245 | return []HostOverride{}, err 246 | } 247 | 248 | // else 249 | return container.Overrides, nil 250 | } else { 251 | var container struct { 252 | Status string `json:"status"` 253 | Message string `json:"message"` 254 | } 255 | 256 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 257 | return []HostOverride{}, err 258 | } 259 | return []HostOverride{}, errors.New(fmt.Sprintf("%s:%s", container.Message, reqErr)) 260 | } 261 | } 262 | 263 | func (opn *UnboundApi) HostEntryRemove(uuid string) error { 264 | // endpoint 265 | var endpoint = opn.EndpointForPluginControllerMethod("unbound", "settings", "delHostOverride") 266 | var fullPath = fmt.Sprintf("%s/%s", endpoint, uuid) 267 | 268 | // create our Request 269 | request, reqCreationErr := http.NewRequest("POST", fullPath, nil) 270 | if reqCreationErr != nil { 271 | return reqCreationErr 272 | } 273 | 274 | var response, reqErr = opn.Send(request) 275 | if reqErr != nil { 276 | return reqErr 277 | } 278 | 279 | if response.StatusCode == 200 { 280 | var resultContainer struct { 281 | Result string `json:"result"` 282 | } 283 | 284 | if err := json.NewDecoder(response.Body).Decode(&resultContainer); err != nil { 285 | return err 286 | } 287 | // else 288 | return nil 289 | } else { 290 | var container struct { 291 | Status string `json:"status"` 292 | Message string `json:"message"` 293 | } 294 | 295 | if err := json.NewDecoder(response.Body).Decode(&container); err != nil { 296 | return err 297 | } 298 | return errors.New(fmt.Sprintf("%s:%s", container.Message, reqErr)) 299 | } 300 | } 301 | 302 | func (opn *UnboundApi) HostEntryExists(host string, domain string) (bool, error) { 303 | if _, err := opn.HostEntryGetByFQDN(host, domain); err != nil { 304 | switch err.(type) { 305 | case *coreapi.NotFoundError: 306 | return false, nil 307 | default: 308 | return true, err 309 | } 310 | } 311 | // else found something 312 | return true, nil 313 | } 314 | 315 | func Filter[T any](vs []T, f func(T) bool) []T { 316 | filtered := make([]T, 0) 317 | for _, v := range vs { 318 | if f(v) { 319 | filtered = append(filtered, v) 320 | } 321 | } 322 | return filtered 323 | } 324 | -------------------------------------------------------------------------------- /config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | opnsense 5 | 6 | 7 | Disable the pf ftp proxy handler. 8 | debug.pfftpproxy 9 | default 10 | 11 | 12 | Increase UFS read-ahead speeds to match current state of hard drives and NCQ. More information here: http://ivoras.sharanet.org/blog/tree/2010-11-19.ufs-read-ahead.html 13 | vfs.read_max 14 | default 15 | 16 | 17 | Set the ephemeral port range to be lower. 18 | net.inet.ip.portrange.first 19 | default 20 | 21 | 22 | Drop packets to closed TCP ports without returning a RST 23 | net.inet.tcp.blackhole 24 | default 25 | 26 | 27 | Do not send ICMP port unreachable messages for closed UDP ports 28 | net.inet.udp.blackhole 29 | default 30 | 31 | 32 | Randomize the ID field in IP packets (default is 0: sequential IP IDs) 33 | net.inet.ip.random_id 34 | default 35 | 36 | 37 | Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled as part of the standard FreeBSD core system. 38 | net.inet.ip.sourceroute 39 | default 40 | 41 | 42 | Source routing is another way for an attacker to try to reach non-routable addresses behind your box. It can also be used to probe for information about your internal networks. These functions come enabled as part of the standard FreeBSD core system. 43 | net.inet.ip.accept_sourceroute 44 | default 45 | 46 | 47 | Redirect attacks are the purposeful mass-issuing of ICMP type 5 packets. In a normal network, redirects to the end stations should not be required. This option enables the NIC to drop all inbound ICMP redirect packets without returning a response. 48 | net.inet.icmp.drop_redirect 49 | default 50 | 51 | 52 | This option turns off the logging of redirect packets because there is no limit and this could fill up your logs consuming your whole hard drive. 53 | net.inet.icmp.log_redirect 54 | default 55 | 56 | 57 | Drop SYN-FIN packets (breaks RFC1379, but nobody uses it anyway) 58 | net.inet.tcp.drop_synfin 59 | default 60 | 61 | 62 | Enable sending IPv4 redirects 63 | net.inet.ip.redirect 64 | default 65 | 66 | 67 | Enable sending IPv6 redirects 68 | net.inet6.ip6.redirect 69 | default 70 | 71 | 72 | Enable privacy settings for IPv6 (RFC 4941) 73 | net.inet6.ip6.use_tempaddr 74 | default 75 | 76 | 77 | Prefer privacy addresses and use them over the normal addresses 78 | net.inet6.ip6.prefer_tempaddr 79 | default 80 | 81 | 82 | Generate SYN cookies for outbound SYN-ACK packets 83 | net.inet.tcp.syncookies 84 | default 85 | 86 | 87 | Maximum incoming/outgoing TCP datagram size (receive) 88 | net.inet.tcp.recvspace 89 | default 90 | 91 | 92 | Maximum incoming/outgoing TCP datagram size (send) 93 | net.inet.tcp.sendspace 94 | default 95 | 96 | 97 | Do not delay ACK to try and piggyback it onto a data packet 98 | net.inet.tcp.delayed_ack 99 | default 100 | 101 | 102 | Maximum outgoing UDP datagram size 103 | net.inet.udp.maxdgram 104 | default 105 | 106 | 107 | Handling of non-IP packets which are not passed to pfil (see if_bridge(4)) 108 | net.link.bridge.pfil_onlyip 109 | default 110 | 111 | 112 | Set to 0 to disable filtering on the incoming and outgoing member interfaces. 113 | net.link.bridge.pfil_member 114 | default 115 | 116 | 117 | Set to 1 to enable filtering on the bridge interface 118 | net.link.bridge.pfil_bridge 119 | default 120 | 121 | 122 | Allow unprivileged access to tap(4) device nodes 123 | net.link.tap.user_open 124 | default 125 | 126 | 127 | Randomize PID's (see src/sys/kern/kern_fork.c: sysctl_kern_randompid()) 128 | kern.randompid 129 | default 130 | 131 | 132 | Maximum size of the IP input queue 133 | net.inet.ip.intr_queue_maxlen 134 | default 135 | 136 | 137 | Disable CTRL+ALT+Delete reboot from keyboard. 138 | hw.syscons.kbd_reboot 139 | default 140 | 141 | 142 | Enable TCP extended debugging 143 | net.inet.tcp.log_debug 144 | default 145 | 146 | 147 | Set ICMP Limits 148 | net.inet.icmp.icmplim 149 | default 150 | 151 | 152 | TCP Offload Engine 153 | net.inet.tcp.tso 154 | default 155 | 156 | 157 | UDP Checksums 158 | net.inet.udp.checksum 159 | default 160 | 161 | 162 | Maximum socket buffer size 163 | kern.ipc.maxsockbuf 164 | default 165 | 166 | 167 | 168 | normal 169 | OPNsense 170 | localdomain 171 | 172 | 173 | admins 174 | System Administrators 175 | system 176 | 1999 177 | 0 178 | user-shell-access 179 | page-all 180 | 181 | 182 | root 183 | System Administrator 184 | system 185 | admins 186 | $2b$10$YRVoF4SgskIsrXOvOQjGieB9XqHPRra9R7d80B3BZdbY/j21TwBfS 187 | c3NoLXJzYSBBQUFBQjNOemFDMXljMkVBQUFBREFRQUJBQUFCQVFEdnErd3RwSXpEbEdvSGlLanN6YzczWGRFeGdta3g1SDd1aTIwelQ1M1h5c2lmL0pwbkFsN2xNVUJNVnlwVFRIZElScmV5OVFBZTJCNkViSHEwdXZua1pHYm5LWDh5OG1TdUxRcjN1TndZdi9ITE1aUmY5MlFNT3R4ZDFBZ1RDQ1JHd0hPT3llMzBBRU9VNmovZDhWUVdLbGhldWcvdmRHeUVPOGVBdWhqL3BVK0svL3Z6cTF0ZldqcVNFREJYd2dVUldld29SNXlHYjlycWEzUmo3NnBiOWZsT2puWmN4YTJFeG4vcWQwNi9WY0Q0S2VSUk9jcGsrOVZYdXFURG9RS3FLejBtMFQ0UzBiU084MUlleFJYTWcxdUhYRGNYUTcxSkhJWTBzMzdvN3kwTm9KYjZWSVZ3V0lGUVRES3NjOTdBU3VjbXA5M0QxV05UTVhVcHFzQ2IK 188 | 0 189 | 190 | 191 | 5GWbPwKfXVLzgJnewKuu1IPw2HS7s510jKHmTM+rLA1y9VfEFE57yj/kJiWbXREB0EgpBK48u4gnyign 192 | $6$$NKsOz2RKrUYnVAIF1GCf2IoFoS7nU35Z.Xsk1PN6SCeCZEl/ZYWvMHFEDi0ICdhxAPW/Y4A5.xfFMhjiInQ0f. 193 | 194 | 195 | 196 | 2000 197 | 2000 198 | Etc/UTC 199 | 300 200 | 0.nl.pool.ntp.org 201 | 202 | https 203 | 5a3951eaa0f49 204 | 205 | yes 206 | 1 207 | 208 | 1 209 | 1 210 | 1 211 | 212 | hadp 213 | hadp 214 | hadp 215 | 216 | monthly 217 | 218 | 219 | 220 | 221 | enabled 222 | 1 223 | 1 224 | 225 | 60 226 | aesni 227 | 228 | 229 | 230 | 1 231 | em1 232 | dhcp 233 | 234 | 235 | 236 | 237 | 238 | 239 | 1 240 | dhcp6 241 | 0 242 | 243 | 244 | 1 245 | em0 246 | 10.2.0.1 247 | 24 248 | track6 249 | 64 250 | 251 | 252 | wan 253 | 0 254 | 255 | 256 | 1 257 | 1 258 | openvpn 259 | OpenVPN 260 | group 261 | 1 262 | 263 | 264 | 265 | 266 | 267 | 268 | 10.2.0.2 269 | 10.2.0.200 270 | 271 | 272 | 273 | 274 | 1 275 | 1 276 | 1 277 | 278 | 279 | 280 | 281 | public 282 | 283 | 284 | 285 | 286 | 287 | 288 | automatic 289 | 290 | 291 | 292 | 293 | pass 294 | inet 295 | Default allow LAN to any rule 296 | lan 297 | 298 | lan 299 | 300 | 301 | 302 | 303 | 304 | 305 | pass 306 | inet6 307 | Default allow LAN IPv6 to any rule 308 | lan 309 | 310 | lan 311 | 312 | 313 | 314 | 315 | 316 | 317 | pass 318 | wan 319 | inet 320 | keep state 321 | Allow SSH access 322 | tcp 323 | 324 | 325 | 326 | 327 | 328 | 22 329 | 330 | 331 | 332 | pass 333 | wan 334 | inet 335 | keep state 336 | Allow incoming WebGUI access 337 | tcp 338 | 339 | 340 | 341 | 342 | 343 | 443 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | ICMP 353 | icmp 354 | ICMP 355 | 356 | 357 | 358 | TCP 359 | tcp 360 | Generic TCP 361 | 362 | 363 | 364 | HTTP 365 | http 366 | Generic HTTP 367 | 368 | / 369 | 370 | 200 371 | 372 | 373 | 374 | HTTPS 375 | https 376 | Generic HTTPS 377 | 378 | / 379 | 380 | 200 381 | 382 | 383 | 384 | SMTP 385 | send 386 | Generic SMTP 387 | 388 | 389 | 220 * 390 | 391 | 392 | 393 | 394 | system_information-container:00000000-col3:show,services_status-container:00000001-col4:show,gateways-container:00000002-col4:show,interface_list-container:00000003-col4:show 395 | 2 396 | 397 | 398 | root@10.0.3.2 399 | 400 | /api/freeradius/user/setUser/fbdbe6a9-2e68-42a2-b410-20bc2537e063 made changes 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | wan 414 | v9 415 | 416 | 417 | 418 | 0 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 0 428 | 0 429 | 0 430 | wan 431 | 192.168.0.0/16,10.0.0.0/8,172.16.0.0/12 432 | 433 | 434 | W0D23 435 | 4 436 | ac 437 | 0 438 | 439 | 440 | 441 | 442 | 0 443 | 444 | 445 | 446 | 1 447 | 1 448 | 449 | 450 | 451 | 452 | 453 | 0 454 | on 455 | strip 456 | 1 457 | 0 458 | admin@localhost.local 459 | localhost 460 | 461 | 462 | 0 463 | /var/squid/cache 464 | 256 465 | 466 | 100 467 | 16 468 | 256 469 | 470 | 471 | 472 | 0 473 | 2048 474 | 1024 475 | 1024 476 | 256 477 | 478 | 479 | 480 | lan 481 | 3128 482 | 3129 483 | 0 484 | 0 485 | 486 | 487 | 4 488 | 5 489 | 490 | 2121 491 | 0 492 | 1 493 | 0 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 80:http,21:ftp,443:https,70:gopher,210:wais,1025-65535:unregistered ports,280:http-mgmt,488:gss-http,591:filemaker,777:multiling http 503 | 443:https 504 | 505 | 506 | 507 | 508 | 509 | 510 | 0 511 | icap://[::1]:1344/avscan 512 | icap://[::1]:1344/avscan 513 | 1 514 | 0 515 | 0 516 | X-Username 517 | 1 518 | 1024 519 | 60 520 | 521 | 522 | 523 | 524 | OPNsense proxy authentication 525 | 2 526 | 5 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | md5 538 | 0 539 | 540 | 541 | 542 | LDAPS 543 | 544 | 545 | 546 | dc=example,dc=domain,dc=com 547 | 548 | 549 | 550 | 551 | 552 | 1 553 | 0 554 | 0 555 | 0 556 | 0 557 | 0 558 | files 559 | 0 560 | 0 561 | 0 562 | 563 | 564 | 565 | 566 | 1 567 | em 568 | em 569 | 570 | 10.10.10.1 571 | 255.255.255.0 572 | 573 | 574 | 575 | 576 | 577 | 578 | 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | test 588 | 10.10.10.2/24 589 | 590 | 591 | 592 | 593 | 594 | 5a3951eaa0f49 595 | Web GUI SSL certificate 596 | LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUZiekNDQTFlZ0F3SUJBZ0lKQU9DSU5LLzhkbDBuTUEwR0NTcUdTSWIzRFFFQkN3VUFNRTR4Q3pBSkJnTlYKQkFZVEFrNU1NUlV3RXdZRFZRUUlEQXhhZFdsa0xVaHZiR3hoYm1ReEZUQVRCZ05WQkFjTURFMXBaR1JsYkdoaApjbTVwY3pFUk1BOEdBMVVFQ2d3SVQxQk9jMlZ1YzJVd0hoY05NVGN4TWpFNU1UYzFNalF6V2hjTk1UZ3hNakU1Ck1UYzFNalF6V2pCT01Rc3dDUVlEVlFRR0V3Sk9UREVWTUJNR0ExVUVDQXdNV25WcFpDMUliMnhzWVc1a01SVXcKRXdZRFZRUUhEQXhOYVdSa1pXeG9ZWEp1YVhNeEVUQVBCZ05WQkFvTUNFOVFUbk5sYm5ObE1JSUNJakFOQmdrcQpoa2lHOXcwQkFRRUZBQU9DQWc4QU1JSUNDZ0tDQWdFQTdFVnRDQ3RnMUlEQ1plTTFDbTRWem1tTHlpRnoveUZtClVseXhRV2VIb0VGenVVQ0JlRkFodWNEUklRZ1hkTUFnbEUxY3dXZzVEdXhkOEtyeWFCYVNLSkFPdGdXRFgwOWoKOGZ5Z3hPZTRHd29MOGZTL0J2SVVFTkw0N1hiQUU3V015NmR2VVFjbkJmSWhYTXJDc25ScjQ1ZE95VzlyNHUrQQozR3lqOS9WR084NUlwN3dLZ2VuM3IxMisvMVN5SDNHaWV4emo4N3hIdnJqTE5TT1RqMUlzYzhyRVZHaFd1T3UyCmtvR2NoUHNjSzFnWE5UbkJxRnRIcVVXOUo3cUo3UGw5NkhrNGlGNkg5TnlpSGxpQWF3WGllaFcrOSs5ay9CQk8KWmpiWENlbjBxa3RVc3gzeFJXbVRsd2kwR1EvMllqV2dYSm8wM0Q3a3JrK01XTkoyR3hERmEvQjcvWGRsODdzawpTYncxemV6T2ZoNE1ZTFhaeExNRy9tRkU2V3B5cGN0MEVYRGd4UDdxcWtITWd2RmdpKzlrZmNGbHNQbGJxK05zCm1Nd0ovMEpoWjhyWWlDNlRsMUY3aWVzemMvd3FSN0NkSC9YN1RwY1hjSzA1aWRDTzA1KzNDS2lpUWpoUnh0NHUKTW5jcEhkVGJxNHRjTG1JTlFvQXlOUVNLK3huUnpQdmtQZTQvOGYzcUVsa0JrMVA0anFiVEJmR1F5bUFJTjZoRwo5QTd4cVJ5UE8rbEo3ZFRRangvdzg4QlNEbmVMRGtJcGxHRHZvVnpHSlBIVVV6cHJXNUtnYTdaNVJ6cEU0WFZWCkxhUGFtdUZnQVpUUVZIUkVnUG1jSmgySFU5SndDZHp2eG11WXlFWG5ueW5KODAxaktUcko0RlQxK2h6dmw3TjcKU2MwZlFrWm53eDhDQXdFQUFhTlFNRTR3SFFZRFZSME9CQllFRkRNdmZ2T1ZMdFpXUnh2MkF1SE04T3M1VlNMOApNQjhHQTFVZEl3UVlNQmFBRkRNdmZ2T1ZMdFpXUnh2MkF1SE04T3M1VlNMOE1Bd0dBMVVkRXdRRk1BTUJBZjh3CkRRWUpLb1pJaHZjTkFRRUxCUUFEZ2dJQkFOZEJscnJJcUdjckpZbThMUENVQ3p6UzF0TXBwNkpzdDZUanJSdGcKV1V6UXgzSHFMbVV2QjA5SWZuM2VXMmtwUnpybzV1Nm5EMEZIbDRTeHlHbXRhbHFBNGZCTld0OTQxOG1zTXdqZApUN2FPcmo4MkZuU1BCaTJNVmEwWmJONGp2RTNrZkRlT2pVWW9nQ0tkRkxTZG5UUjZEWjJhbVZaOWFERC9SdWtGCjdHVTMwVGFvb3k0UytxeUpmR2ViTDlxYUNBQ1ExUDg1UEw3OVNCNWJ2ZEM1VUZnTEtZcjdhbFY0TFZFOWhBYU0KY0wrZnFNNENvTTJHT1YyQWwvK0puamdzbmlZSDJqdFh3ZmNwNmhyaUcxZXNKNFpOUUFjelJjTEZzK1R3SVVBRApCNnlnTmk1SWQzQk5taWJaNmpWSyttZmlYZENzSDFQUXJxN0ZsTk5RSEdIMzVWMHlxUVVqMXJuUXIvSlVwanFaCno3a2ZqUWVCY2UwK1FRQVd0cVg3TjhzTFZQQ2RleWx5d3pkQnBDNEtsSHU1dXVZODJtN0lTWVY0TWpaMEFsMEYKY3U4bHdVeWs0K29rcE1rVk52QXJIRW1XZ2tDUWRpaXB1U1ZmT1pNc3BZQXZzdUhKdlBEL3dZMFRxRllzZ0lnNgpCVVNRa080dCtNdXZwVElIcDVGZTcrTktBUTNJczBZa1N5bDMzSzcyQmxtaGVlMm9nZy80TUwyRUZLeU92WWFBCmdvck5BTW9SdTNPRW01VUZzMDdJMW83RVVHYWRjQVlXRmZMZXlTV2wwRmRQV29kTHBkanYxQzVORjBXYVI2WjMKYjFjejNNa3JocXhUZzlEMnRWcVBBNFoyQjJXN1VOT08zTWJUV0ExRUJSZU5wVmlSb1Ywbk9HNTFpa1VtZERGRwprR1BnCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 597 | LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUpRZ0lCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQ1N3d2dna29BZ0VBQW9JQ0FRRHNSVzBJSzJEVWdNSmwKNHpVS2JoWE9hWXZLSVhQL0lXWlNYTEZCWjRlZ1FYTzVRSUY0VUNHNXdORWhDQmQwd0NDVVRWekJhRGtPN0YzdwpxdkpvRnBJb2tBNjJCWU5mVDJQeC9LREU1N2diQ2d2eDlMOEc4aFFRMHZqdGRzQVR0WXpMcDI5UkJ5Y0Y4aUZjCnlzS3lkR3ZqbDA3SmIydmk3NERjYktQMzlVWTd6a2ludkFxQjZmZXZYYjcvVkxJZmNhSjdIT1B6dkVlK3VNczEKSTVPUFVpeHp5c1JVYUZhNDY3YVNnWnlFK3h3cldCYzFPY0dvVzBlcFJiMG51b25zK1gzb2VUaUlYb2YwM0tJZQpXSUJyQmVKNkZiNzM3MlQ4RUU1bU50Y0o2ZlNxUzFTekhmRkZhWk9YQ0xRWkQvWmlOYUJjbWpUY1B1U3VUNHhZCjBuWWJFTVZyOEh2OWQyWHp1eVJKdkRYTjdNNStIZ3hndGRuRXN3YitZVVRwYW5LbHkzUVJjT0RFL3VxcVFjeUMKOFdDTDcyUjl3V1d3K1Z1cjQyeVl6QW4vUW1Gbnl0aUlMcE9YVVh1SjZ6TnovQ3BIc0owZjlmdE9seGR3clRtSgowSTdUbjdjSXFLSkNPRkhHM2k0eWR5a2QxTnVyaTF3dVlnMUNnREkxQklyN0dkSE0rK1E5N2oveC9lb1NXUUdUClUvaU9wdE1GOFpES1lBZzNxRWIwRHZHcEhJODc2VW50MU5DUEgvRHp3RklPZDRzT1FpbVVZTytoWE1ZazhkUlQKT210YmtxQnJ0bmxIT2tUaGRWVXRvOXFhNFdBQmxOQlVkRVNBK1p3bUhZZFQwbkFKM08vR2E1aklSZWVmS2NuegpUV01wT3NuZ1ZQWDZITytYczN0SnpSOUNSbWZESHdJREFRQUJBb0lDQUJNSjhTUkVZcFFkSUEwWHh2RmxONHFmCmhLMHdEdW5USml5aTNZRzR0dndaNmhwV2NWaGhsS1lrUEhYZDhnM3RZWEt4M1RTVWtteDZiWU4wTXY1aU96cmIKaU9Qd0E4c05XYTlwUFFkQTZOdjg3a044Qmx5bjZ5Z0Q2QjB5Z1gzVkZsaGUwS0NGNUFZZG9jU1piaUQxTXJCdgpRK0VGZ25zUjg1OVBmZE1BUjcyUC9OalBWVVZzdGhIQ2l4NkdFNmhtL3NITzdTdDUwNG94MStZYlRNdXl3blErCk5aM2Jub2xlTFNNWElLYXltVzJBdHJZS1JtbXJtVld4a2ZGK25aaWo3aHBxa2p5aTZXKzR5N09JVENqVG01RmMKNlR1UFplTE42Wk5nL2VrRm1qcVN3V3VCa1N5WHVsWGtWS2JrVzJWRWp2eUhUSlVtMkVTWGttYWg1dlI5WUhzUApqV2dsM2tldVl1QkxtQ3hHbzZhWEx1a2YvSVFGYTZ2akY2dE9BSWN1Zk0xZzNHKzBRTFl4QzRHL0J6K1pHcUlVCmY0MVQvVWd6WnJMc3NrdjBvbm9jeDZuTFE5QkNMdXRNYXVxYXVhK3UzTHd6dUVXMENkcmx5bU1OMmtEWU9pbFkKS0RqZHJqSzM4NzdUV0tmVjZ3VnhGWGZDK1pPbENvbG4xTHk1TnFlQ2tuL2g3N1dXVUhZNnVPb2lPTlA2L3FQZgpsbFJMaVBBeVFPTUduZDBuZWdVUzNZNjNLZjZpYkRHclB4UFMxb2FId2JZU2d4ZC8yUFViLzdlbGg1REFqekNLCjVJYnVoMzNlT0g4cklrelU2a1JsTjJZUlAwNFoxL1h5YXFrNkJRdjVrbFlYVm5tU0NHSEp5bTdHL21DaTdVZzMKT3RrcU9ueE9YaGVhQmowcUg0SXhBb0lCQVFENG9vNjJzRmFQeUNqemNuSm1iTU8wd2E4VUFPRGpwR2ltUmwzaQpKdFBYUXZIR3R5RCt2T2xPVm9tdURCNGdRWWpmeXRUR3NYc3k0YkgvNHpaR3E3T1VvYXY1bnRxUnk1L2tiaXNCCk0wSlZCeHE0bmZtVGVKeUlBZmt3c0NmQzJrZ2d3RkdFU0Z4SWt5VzR5bHFLc01GaUtVWjRNY3FEY25zaDFFSloKZUZENThiQVdha2VIaWozZnk2MkduV2dGN3g0UENhVklEd3luRG5UUDdTanA0QzZaVG1SOWJPRzRIblV2QVJaQQpaa3lkV2tRc1JGeHFCRWROMlRaOFhlY1BSaDlIb0ppVDdpWkJ6MVRSZkRxYzQ3cWVrazk3NTRycG05QlZMM082CmNKL0dkSGNuWGd6WE9TdHU3Zmp6N1QzQXB5TWF1dDVrd0x2VnhOM0RHcVorNG5HSkFvSUJBUUR6UlJ5U3U5YzEKLzF4dldlM2h6M2RIMGJ0dDVoTXNoWkhFVXJwZ053UGlFQkhkamt3VXRsWXJ3WFhlTmJhbXBlM01KZVdUY081ago2RXhpSDk2MEk0TjdOTDkyMm55Uk1MMDM5b2tJdDhueXoxREtqUnZNTy9FQmJaOXlTYXY4SVBwbE1leUtBVm1jClVrQUd4UDA5SW5abFhJM1RBa1NHVUdlQjh3d0k4cUhuazRVTVFiRllSTUVUQkZCa3YxY2MzR1lCN2lsUlR3Z0wKWllSMUJscWM0ck5NZGpOREY1U1ExMG4vcU1GeEp1dURXWkwraisyeDdGVWltNVY3dWtIWm1BRE9sUjJibXVyUgpmTU5kYkRGNllyY28zR0FyaVE2aDZQcmZWdmk4SDRGc1VTYnpXRFcyTzkxQXlXN0VjRnp4b2RYZ2MycjVOTExRCmRaeU93SW9kM3kxbkFvSUJBQjhHbWV4dUlMOGNhUS9IN2tLZHUrWW9iU0ovNFpCR2lkQ0Y0MTAvSHh3emZGd2gKcWZwZnRIVlVFeVltMlBPSmVmMERJSDRTMDU3THp4eHhTK3FScm4wVGw1UTBvRzJsRFRUQ0VwZTV2OE5BZWJNago4MnJWbUNMWXJESEpLWTBGRkE4U01KbmpOYkRRdTlwTlZmTU1qM1VpVldyV084RWZYZ0lnckk3aGxxazU0WkZLCmZkYUtCNktQbGYzQVVxUzY2L05RYnRHSkh6a1JjcjRuaC8xM1BobGZVT2JkMldUU1dDa2ZaNWx0cW8zUUg4V3UKV2lIWW10VTZEN1NCT3o0S3NBaU9IN3dGOGJ3d2xSTDIvNUZvVVhkTUpxTDloN1lTL1hKRDA1c21ScW5MQ3J0YwozeGxVUnZrMnRPUXJiSk5IeC9lajdmQ0FwRy9PZXlYSGc1TTl5cEVDZ2dFQVNHNW1jSVgvTVBPa1dQOGtwZHc0CnZxaUNydGtYRW1WK25qNm5nV2cvL3JvY0o2UnJvS3NkZ3crcUFZeHFvcm02ME5ManhQK1Y2eWRLUHRrUVhRQkoKOEpBbkJjTk4zWWp1ZmRBb3d2Qzk3MDZzMW5Jbk9hc0xPZ3Fpczh1ZHFvZERKb2d6em05U2VBbkJTSUswaDlSUAovaVFOa2lzVnJnd1lsWWVCS05UZFFlOFphU25TSE43endhN0NKUTBYYWQ5eGU5ZW1jN0FkVEE5ZzNkc1RkYXpHCkI5a1ZzRDlBRzlRT0UxSHlycmNRM2wzNE4xVXhSNDEvVjd1TlNYYU9qclFFWVgzaWYrY2pUVlpoY05wNjdONmgKZkVnSlZrMExqMGVvRW9GNXM4R0pybStITW1Nc011TW1JRmtaWXVHMXVyZ2R6eU51VVY3UWN1TGh4MXNxaEhSagp0d0tDQVFFQWtXYlZHTEpZM1FrSCt4WURYVEhqVnBIR0pLYmdZSmxoQXlUalIyNXVGTFppWkEyemFzRUdnVVhUCnFtc3RGc1VaRjU4di9UWGw1WTREazR0WGlsYkpqTnRmWmdxb05KOXM0Z0QvQklqd1psaUhNVzh1RlhYRHg0bUwKRVdmUEo4UUFQUjU4dUNhSU9BaldncGNkQTlYVzhaTzhrNGtSYTZoV0NVL3FObFVSRkJiRzJlZ1NQellJRzBGdAp6Y1REU2s3YUN2bjZ3d3F3bk8vRzJIQXNsazZvWVdzOU0xM3dueE90Qk9kMEhUMWhERW9qNnBzTG02MTFDMGNVCkYxNE1ZcmJ6K1ozeUxNWVlYZVU0bXd2dkRpRThjWXhiS3pOMW1kU3kwQnFyVFo3T1ExZktlZlREcnlKUnBjK0wKZkVWMHRSOXpicUZyRW1lNEpPZjU0aEhwcEZBT0lnPT0KLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo= 598 | 599 | 600 | 5a59c851eb6f7 601 | openvpn-server 602 | 5a59c822be2da 603 | LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUVrekNDQTN1Z0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBREJ0TVFzd0NRWURWUVFHRXdKQlJERVAKTUEwR0ExVUVDQXdHZEdWemRFTkJNUTh3RFFZRFZRUUhEQVowWlhOMFEwRXhEekFOQmdOVkJBb01CblJsYzNSRApRVEVWTUJNR0NTcUdTSWIzRFFFSkFSWUdkR1Z6ZEVOQk1SUXdFZ1lEVlFRRERBdHBiblJsY201aGJDMWpZVEFlCkZ3MHhPREF4TVRNd09EVXdNalphRncweE9UQXhNVE13T0RVd01qWmFNRzB4Q3pBSkJnTlZCQVlUQWtGRU1ROHcKRFFZRFZRUUlEQVowWlhOMFEwRXhEekFOQmdOVkJBY01CblJsYzNSRFFURVBNQTBHQTFVRUNnd0dkR1Z6ZEVOQgpNUlV3RXdZSktvWklodmNOQVFrQkZnWjBaWE4wUTBFeEZEQVNCZ05WQkFNTUMzUmxjM1F0YzJWeWRtVnlNSUlCCklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUFwWVRIQUtoelhGdklxUVA4ZXRnRS9jQkYKZG9VWGhRcXdVSHJrSXBOWVJFTXZFOUwyMnlLd3IrUDNIdVZ2YW9XQTVkdnpvY240MWZwWlFVVHU4SWptUEFzYwpQYXQ5RVFtSjUwMWh4NFNrZThwWjcycHIrTzNWNjR3QUFSTmU4SVJoZmhNRmNyL3piNXAvMUNqcXhibU9HT1kwClBENjA2V2t3aWtNQ3pEQVZKYWxrRFd6b0pBMjRwMGo5VUVqWmRoZzhqUXc1aXVFNHU5UXpabitZQjVabUxlZDAKcXROejBXRnNoMlNXb1ZkZnQ3bk9QV1Z1clpEbXBxa2ZxcEJ6Ukg4ZVJwMFYzc3dkYyt1cGt2VTR5azlaVE0rVQpCbG50TmJhTVVSNjJpYnFvdENOYUNWMXhUYk9ibDRBclNEM1FQdjB2VytDbFpYa1ZLTFdwWG9OdGlHYVlXUUlECkFRQUJvNElCUERDQ0FUZ3dDUVlEVlIwVEJBSXdBREFSQmdsZ2hrZ0JodmhDQVFFRUJBTUNCa0F3TXdZSllJWkkKQVliNFFnRU5CQ1lXSkU5d1pXNVRVMHdnUjJWdVpYSmhkR1ZrSUZObGNuWmxjaUJEWlhKMGFXWnBZMkYwWlRBZApCZ05WSFE0RUZnUVVvZmxtQVB0Z1kwQzBxdGhCaEIya0tWQmZVWjR3Z1pjR0ExVWRJd1NCanpDQmpJQVVpYzlUCjRBSTRWRUhZZTFtT3JkTkw1RG9RK2d1aGNhUnZNRzB4Q3pBSkJnTlZCQVlUQWtGRU1ROHdEUVlEVlFRSURBWjAKWlhOMFEwRXhEekFOQmdOVkJBY01CblJsYzNSRFFURVBNQTBHQTFVRUNnd0dkR1Z6ZEVOQk1SVXdFd1lKS29aSQpodmNOQVFrQkZnWjBaWE4wUTBFeEZEQVNCZ05WQkFNTUMybHVkR1Z5Ym1Gc0xXTmhnZ0VBTUIwR0ExVWRKUVFXCk1CUUdDQ3NHQVFVRkJ3TUJCZ2dyQmdFRkJRZ0NBakFMQmdOVkhROEVCQU1DQmFBd0RRWUpLb1pJaHZjTkFRRUwKQlFBRGdnRUJBTGxtZ2h0MG9Qd3djVjlFRU1PZmdLR1FVVXplM09kcG5GOXVjZUhtNzdBN3pTWWdFZTl2MXJJaApQRTE5dEFGK3hlY0Nnb2ZRdUlCbWRnYkZYYnBWYkVIeEZaaFVIUDJjNUhqckxOdUtrQUFKMGlzcXJGRGkvOXFRCitXdXBBb3hHTU54K1JtSEJ1dDZaUG5DdURrOURUSHY4Zkx2YlRGRU5VYXhRSytzVnhpTzJEd2JKZTFDcmR6bU8KRCsrVVBLRGtLNGlURWFtaytkQkNvSWR1dWQ2a3BSZExwQW9TczlsUXZ0Q2FYYisxSDVzbDRhQW4vNUZlY0lFcApSaWZ0MDJZeVl3a05zRTRmS1NiYXowZXZSWkxRbFhKQkE3eW9WNEtjRkJhNnpxaEUwOVAxK2pTNXhXZFVsTEZnCjhPSVQwSmFxUm1HRW5NV1YzWVFHTUxrOXVvWHRsMHM9Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 604 | LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRQ2xoTWNBcUhOY1c4aXAKQS94NjJBVDl3RVYyaFJlRkNyQlFldVFpazFoRVF5OFQwdmJiSXJDdjQvY2U1VzlxaFlEbDIvT2h5ZmpWK2xsQgpSTzd3aU9ZOEN4dzlxMzBSQ1lublRXSEhoS1I3eWxudmFtdjQ3ZFhyakFBQkUxN3doR0YrRXdWeXYvTnZtbi9VCktPckZ1WTRZNWpROFByVHBhVENLUXdMTU1CVWxxV1FOYk9na0RiaW5TUDFRU05sMkdEeU5ERG1LNFRpNzFETm0KZjVnSGxtWXQ1M1NxMDNQUllXeUhaSmFoVjErM3VjNDlaVzZ0a09hbXFSK3FrSE5FZng1R25SWGV6QjF6NjZtUwo5VGpLVDFsTXo1UUdXZTAxdG94UkhyYUp1cWkwSTFvSlhYRk5zNXVYZ0N0SVBkQSsvUzliNEtWbGVSVW90YWxlCmcyMklacGhaQWdNQkFBRUNnZ0VBT092aXJCMUNIdjhKa09ab0M2OENlR21JK2V0blhUK0J4d3VjTFMvUzZSYloKdDgyVFMyVXdzaXlKcmJ4bGhwS0c4NFdpMFg3dDdsaDhIWEFoWStNUW1wR1BrcjNJOHZUKzBlYlF1NWFvSWxKQwpmNDF2dUZuQ2VaRFo1NFRMMzVjSEdCNWVmMG4zNCtlVUVsaEg5TnVOUEk3ZVkrR0V3Y2lGQXVkc3JOL1VSZkRmCmNBUW5UM3NOeGt5aURkSmx1dlVETlZkbjEzS0V4dDliemNBSlgwMXU4Vm4vZzJrV3E4Tk1LanN4ZUxlbkhwdkgKR0dvc3ZDeEEvM3RSakFOQXBHSW5WbDhtd29MMWZEeC9KWmgrMEhQcEVvWThBNCsrQ2VtVmdVaWR5UlorR0IxUwpjSTlXV1NRaXByM28rYjI0U25KeGtGZy9PaUJ2RVIwYnZsRkx6ODBDb1FLQmdRRFRWdlljQnVvRGR1RzhrSW1qCndSSUxLUWtVcUd0VUxDZXY0SmdvempsaXJiZHNFTFB6TFIxTHhaaGs3ckVYR1ozU3NtUHh1UVhQRkwxT1ZJUDkKNVRISWdLK1M4V0drZ2RqMk9UUnF4L2dwMWM3WHBLaUppNlJ1dWVKT1lnb1kvbkpINWZhbGJGcVdjV3lJbTFMZApEcmVmdURNaS91akthUklVWTZwY3BOVm94UUtCZ1FESWZ2NUtUOXppVUJFRHMrZjRNa0Z2L3NjTExvbEticGliCkYwWHMyNDRPcXk0NnA1dzREUEtCZzg5SmU5ZkpTT2pmVGFlc2l6Y1g1QnU5UXdDcEsrM3k0UmVNVUQwbTFRU1oKcUFwblhMT0pRczZBaGkzencyMXNMckNwU2VBN043Vnk4QVNPbFhlQ1Nhc01lUzdPUzBCYjFkRVc0RDJpSDhEZwpuS2FRYnZraWhRS0JnUUNUZ1lpNU95SXVWTlZ0dFR3OExVK1l0b1YrQTZjVjB0UEliK3dEM3h3eWdha2FKZlpUCmJsT2FSVW4rZUpMbDJwMUxKL09XRFZ1K2syMGx0TnVEWkdzQTFNQzJ3UDh6d09WYnhjV09HaEJkc3J0eHk1MU8KazBhcmVkWTRlemt0Qkx0aTcyRGRTT2xaUThWSys0NDBlRWMxbFcwcnFkdDFHeXpoU2tibjdxeDlPUUtCZ0YyQQpNOUN1QytHUzIweHh3dlZSWW9qN05SSDI0dG5PVitiRDVMMC82ckRXamRtV3Z0aTQyUU1qV0RENXl6azRnamxaCk9wL1IwS0NZcFlNTzB5a1Jyb2M5QjhGTXppZms0WjlTNlg4b2tTV3EvU2ljNnFNcWVVWWhSQXUrd3c1NnZXamIKbkJSdWlldzl0TUk5WldHWllHc3pmSzRCWjF6TEdDVG5pUm9FVnJVTkFvR0FmajFDaGptT0wzMGlDUU1UTTVZZApLbGl0VUx6ZVZ4WFVwTmxqNHdHbXJjYStqRXZYbGdCWnJXSVF3WmJrN1NqdDlINFdydm43dmc3cldTY21sajVhCkZjck8rMVcyZDIvUjQxd2cyZWRrU0kzS3dwalpaSytyZGFQZERZL21leld4VExVM3ZvUHZReTdZREVVL1dnNkwKVS9yUHlmeXE4cS9jeWUyRGhOMWJNajQ9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K 605 | 606 | 607 | 5a59c822be2da 608 | testCA 609 | LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURwVENDQW8yZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREJ0TVFzd0NRWURWUVFHRXdKQlJERVAKTUEwR0ExVUVDQXdHZEdWemRFTkJNUTh3RFFZRFZRUUhEQVowWlhOMFEwRXhEekFOQmdOVkJBb01CblJsYzNSRApRVEVWTUJNR0NTcUdTSWIzRFFFSkFSWUdkR1Z6ZEVOQk1SUXdFZ1lEVlFRRERBdHBiblJsY201aGJDMWpZVEFlCkZ3MHhPREF4TVRNd09EUTVNemhhRncweE9UQXhNVE13T0RRNU16aGFNRzB4Q3pBSkJnTlZCQVlUQWtGRU1ROHcKRFFZRFZRUUlEQVowWlhOMFEwRXhEekFOQmdOVkJBY01CblJsYzNSRFFURVBNQTBHQTFVRUNnd0dkR1Z6ZEVOQgpNUlV3RXdZSktvWklodmNOQVFrQkZnWjBaWE4wUTBFeEZEQVNCZ05WQkFNTUMybHVkR1Z5Ym1Gc0xXTmhNSUlCCklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FROEFNSUlCQ2dLQ0FRRUEzNVhBUTh1TnNIQ25RNGo2UTlQb3ZNamMKbklZVWZiMEtDZTJMWTNQdzhweXJZM29BaDRUT0RhRENmZWo1dHNqK2IzUU0zbXhyLzlDQ24xNVhUMk9VaGRJdQorZmhsL0wwTVIyNERMUW9DQVk4QmpIaEFRSFN5V3lPYnlzaGdUbSttYWhWeXo1QVdsZGI5RExaenM5Nis3NFRICjdhOTdZbDE2aU9ULzhmRVYrZHRwVnk0Y2tSSDFhVlFiOUN5ZGVDVXlPZjR3OVZUWTFsLyt1WEhIOGpUbHRJdmIKV3VDZmxKWE4xYnhBeXQwcHJabVhlV29LZXdmN2l1YlNGT1RScHh3MElQM0piQWxCWkNCaEpTZFJNcTQ0R2JOdgpuMmFxa3BSTGlVemFwbVA1SnZRbUY3OTNzMUhCSTJMd0VjanFiWVBXY3ZPbW5jQWFQWlR1OGw2Q2s5TVlkUUlECkFRQUJvMUF3VGpBZEJnTlZIUTRFRmdRVWljOVQ0QUk0VkVIWWUxbU9yZE5MNURvUStnc3dId1lEVlIwakJCZ3cKRm9BVWljOVQ0QUk0VkVIWWUxbU9yZE5MNURvUStnc3dEQVlEVlIwVEJBVXdBd0VCL3pBTkJna3Foa2lHOXcwQgpBUXNGQUFPQ0FRRUFEM3lCT2lRbnI3Nmc5bHkrWDViV09hNStER2Z6SGVsc0w3QW5OcWZndkQ3SlR4aGJBYVRtClJhbGZDNXFaQkVDSHJIOERsZ1gxWTJBR3FnU29BcHNyeCtIamxQT2V6bDhBd0s5UUw0WTZTSXU1Y01GUGtxQWoKTnBWOFZ4clQzWk95TWNjRzlxQlJEQTFKVk9kcCtoK3VTZklVTjVVSHV5RWFuZHVFcUwwK0wwRlVDN3lQaWJEOQoxTlNCdWZmNEdPVEFXNGxncDN3U2FJbDd2dVhRaDRHSXJrVkNCMUpWWWNHMDZVVk5WKzV4ajM1QVdUOGx1QlVNCk4wZ0kwYUhLaUtuSmNZempYZ3k1MWdHZEEyRE1DUllKa09vclRxbmRiUWFvYVY3SjR2QTc1UzFLNE1PVXN0ZWQKemlxdklDRG9vTlozMkRjb1BxM2htbWJ1dXNTUndMNW5UUT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K 610 | LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2UUlCQURBTkJna3Foa2lHOXcwQkFRRUZBQVNDQktjd2dnU2pBZ0VBQW9JQkFRRGZsY0JEeTQyd2NLZEQKaVBwRDAraTh5TnljaGhSOXZRb0o3WXRqYy9EeW5LdGplZ0NIaE00Tm9NSjk2UG0yeVA1dmRBemViR3YvMElLZgpYbGRQWTVTRjBpNzUrR1g4dlF4SGJnTXRDZ0lCandHTWVFQkFkTEpiSTV2S3lHQk9iNlpxRlhMUGtCYVYxdjBNCnRuT3ozcjd2aE1mdHIzdGlYWHFJNVAveDhSWDUyMmxYTGh5UkVmVnBWQnYwTEoxNEpUSTUvakQxVk5qV1gvNjUKY2NmeU5PVzBpOXRhNEorVWxjM1Z2RURLM1NtdG1aZDVhZ3A3Qi91SzV0SVU1TkduSERRZy9jbHNDVUZrSUdFbApKMUV5cmpnWnMyK2ZacXFTbEV1SlROcW1ZL2ttOUNZWHYzZXpVY0VqWXZBUnlPcHRnOVp5ODZhZHdCbzlsTzd5ClhvS1QweGgxQWdNQkFBRUNnZ0VBSzh0a1pxTW5kTWtNS2xGWlhCSFZBNjJBY1BSZWJTYXJJYml5MWQ4dThnYTQKRjNzZFFXNUZBaXhjREZlbkdpT1NtdmdyVVNJQm9aRVJGUEJndjc4c3AyMjlIOStFOHBXQkl6aXNUSlVxUVczbQppc0kvSzZEd0VxUU43eEdDczdwdzZWU2NNWVh5dHBUdTZoK08yRXVvTUxoY2hQVWJnTy80Z1hvQm5EMXg1WWV6CjluTmhYeXJZbW9ESlFYOGZxajVFcTNucldkTk1kTWJvRk9zSDVGN1NBRk9rNWRDa2dzODBLZzNSeVp0YUZkRDQKS3l3QVFDZEV0OWFQN2o2TkhLY2hyQlJVS09XK1pDekgrOU9yUTB2S0RzWU1YbmtiZWFIWis3dmdDaTlVc1VvMwozeHFvalZNbEIxRXd2ck5pNGtHY3B2dk80dk1pSnBaWkg4c3pUUWlHZlFLQmdRRDRrTndxazdnbW9TOHZhTE1mCmgrUU5TK0JuTUhNc09IK0lTQUVHTUFFTUsyU0tEbjg4dC85aThZYzVqZEU0a3p5akt4SnVkUENaQk9BdEY1ZWEKWlF6SFJYZ1R5V2xvM3U3ZktVekNZa1AzM1daT0cyUG1LUktnSk5xbUwySElBQUZEbUl1M3pPbFJlaUN1Z0lwUApLVUZrZFVZd21XcFhyUmJBSi85SnE5L01od0tCZ1FEbVJhQVFJYTV5bWU0V1c3bWFkL3ZFUEJsaVpKdGFGLzhkCjdDbllmQ0YrMEtkc096Z2VjMHd4VUE1RGRMUDJXQzd0ZTZDLzVhS0JXaS9qejgyeWhJWDB3cXlTaUQ0YVFIMFEKOER2NWQyNWtVZXFzTkUvSkg2eGt5T1lJM3FRKyt0RndMazR1R2hDUUtISmdNM1A3QkZxM2liazZ4ZVVrdndkRgpzNUpGMkE5T0l3S0JnRmtQdm5OYS9tNkk3bGswVUlvSnJNSyszeWJhQzBwYTdBY3VsWDljRCtRR1lEMi9PQVBQCmdhZzRGbFdlNU1vNnAwMW5qM0VZWVdUU2hHaGp2YVJLZEt1cHpuNTRlbFpqR24vSFVvT0xwZ0xYeDJKUkdoaEwKdXlxNlNjV2wwSWxTeHlFck5WU2tEUzF1YnV0WGp6Y1I5eVpCaHVhKzhZVjh0VndnZUs0eThUdGRBb0dCQU45bApWSy9SdlVmNUJmNHk1cEZ4TFpObktzbEdDV0VTUHJKczF2dnJFU1BTa1ZweTZUTEJjSDIyeU4rd2JKYmxYa0dPCjJwalEweUxpdCtzdlFzT1p4Y3Q2d2FrMisrakQvNUZiUHhQNlJlS1ZoakdpWG5Va2dUOFZsL1dxNlhVZ0orZkoKUkpkOU9leGhFUFU3ZFoxazBBMlhVMWd0Zk94MVZ6ekx4WEIwK0FRNUFvR0FDaVFQUFhNdzZ0Q3JrenJRempXTgpoOTVHeDBBd2RvdkdiVDl6M3BWellJb0ZBN3ZYcnFFZUN6U1RpUWdzWHIrTlhwM29STjc4TituUUp0bG1HL3AyCnprRUhHN1ZXVjkwdWo0bk9ZQVFlSmE1aDFycGZ2UkM1Zjd4WkFYT2Y5Q0dCNlJuTGErZ3lRZ1U5bHhmMVU2QnIKcW9kZ3NEZ0x2bWF1SmpzKzNwdEQzaUE9Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K 611 | 1 612 | 613 | 614 | 615 | p2p_tls 616 | UDP 617 | tun 618 | 1194 619 | test-openvpn-server 620 | AES-128-CBC 621 | SHA1 622 | none 623 | 172.20.0.0/24 624 | yes 625 | yes 626 | 0 627 | 1 628 | 1 629 | 1 630 | wan 631 | 632 | Iw0KIyAyMDQ4IGJpdCBPcGVuVlBOIHN0YXRpYyBrZXkNCiMNCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0NCjI1MjgyNDlhNzVhNTA2MDcxYTkyMjZlZmIxOGI3Y2U3DQo2YmI2NjA0ZjRhOGVmMmE0MzIzMzNlNDliOTdmZGQ1MQ0KNDdkNjAxMmIzMzhmYTA1MmI5OTZhNmUyYTIxYzBjNGUNCjExYTEwNjU5MzY4MDA2NDJmN2ViYTEwOWM5YzgxZDYyDQo5NjEzMWM2NTY4NDg5ZDc4NGVkMDcwYTZlMzdiMzQ5Yg0KNzI4OWNjMDc2ZTRmODcwNGFjYjEwYjVjZDdiYWNjOGENCmVlMzliZTBiNGY2ZDUzYzVmNmMxNjM1Y2MzMWQ0MWRhDQoxM2RlYzEwNjFlYmU2OTg3MzMzMjJlMTY2NzNjNDg5Zg0KYTU1Y2U5NTRiZmQ1MWVhNWM3MDE1MTZlNmM1ODgxMGYNCmU3OWRmMjBmZGU4YWZlMjQyZDU3YmVhMzIzMDI4ODZjDQowNmRlYzMyN2JiZDQ0NThmODU1MjI4YzEyOTI3NjVlNg0KYzlkYjRlMmRmNGNlODFlOTZkM2MzNzVkYTllMTJlMGQNCmJkNTkyZDU0Mjg2NTA3ZmMzOWE3MDI2MjJjNTA1NjJjDQo5NzRhODgyMDFmN2RiMWQzNmQ1ZGFjNzRmYWRjOTEwOQ0KZTBiNTk3NThmODc4OTM4N2U1N2NlZThhNjgzODYzMDUNCjU4ZTIxNDUzYWVhNGQwNGFlYzRlOTE0M2I5YmE5ZjY5DQotLS0tLUVORCBPcGVuVlBOIFN0YXRpYyBrZXkgVjEtLS0tLQ0K 633 | 5a59c822be2da 634 | 5a59c851eb6f7 635 | 1024 636 | 1 637 | 638 | 639 | p2p_tls 640 | UDP 641 | tun 642 | 1195 643 | disabled-dynamic-ccd-server 644 | AES-128-CBC 645 | SHA1 646 | none 647 | 10.10.10.0/24 648 | yes 649 | 0 650 | 1 651 | 2 652 | wan 653 | 654 | IwojIDIwNDggYml0IE9wZW5WUE4gc3RhdGljIGtleQojCi0tLS0tQkVHSU4gT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0KZWI2YzFiNWM1NjA0OTA2ZWU1NjEwYjQxNDkwMzY4MGQKY2Y0MjUwYWQwYTcyMzJlM2YwNWI4OTIwMWUwNzU4ZjEKZDhkMDBjNzFlYzU1ODIzMGRiMTg3ZThiZmFlZjNmN2IKZDNhMzZjNDJmNWRjMjFhMmZlMzM3OTliNDVlYWVkOWEKOTlkMjc4MTFhMWIwYzYzMTdjYWY5MGFmOTlkYjJhOTUKZTQ0YTVjOTA4ZmE0YjNkNjYyMWI4ZDY2OGJmYThhZDcKNDYxZTAyMzNhMTk2OTU1NGQ1NjkxOGUwZWI0ZWI0ZWEKMGNhYTI3MGZmMzQ4N2MzZTdmNzU5MDQwODM4N2FiNzAKODllYjUxZjQ1NTIwMTk2OWI0Y2UyMWYxNDdhY2QxYzkKM2I2Y2MyMWQ2MjQ3YzAyMWQ5NzgyZGMzMGExMTBmYjcKYWE1Yjk2NzlmYmEyNDAwMGY5YzIxZTE3MGU5ZmY2ZTYKMjU1YmU1NmQwZTkxOWMyOWNlYmU0Y2EwNjBmNWYxYTIKNDc0MDc4ZWI2NzQyYzM0ZGY2ODk0Zjc4MGIyZjJhMDIKOWVmYzA5OTZlODI4YzNiZGRiMzYyNWRmMWUwYjQwYTYKNzdiZTIyYTc0NjJmOTZiNjQ5NGUzMzJhZTgxZmQwNWEKODczMzMzMmZiYTEwOTI0Nzc2NGQ1MTAxZWUzMmFiZDIKLS0tLS1FTkQgT3BlblZQTiBTdGF0aWMga2V5IFYxLS0tLS0K 655 | 5a59c822be2da 656 | 5a3951eaa0f49 657 | 1024 658 | 1 659 | 660 | 661 | UnmatchingovpnCCDoverride 662 | 1.1.1.0/24 663 | 664 | 665 | em 666 | 2.2.2.1/24 667 | 668 | 669 | 670 | 671 | --------------------------------------------------------------------------------