├── .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 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/sonarlint.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
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 | [](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 |
--------------------------------------------------------------------------------