├── .gitignore ├── README.md ├── go.mod ├── go.sum ├── main.go └── pkg └── edgeos ├── client.go ├── config.go ├── csrf.go ├── doc.go ├── port-forwarding.go ├── port-forwarding_test.go └── session.go /.gitignore: -------------------------------------------------------------------------------- 1 | edgeos-rest 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # edgeos 2 | -- 3 | import "astuart.co/edgeos-rest/pkg/edgeos" 4 | 5 | Package edgeos provides a Go client for interfacing with EdgeOS devices. It has 6 | been developed and tested against the ERLite running v1.9.1 firmware. Thus far, 7 | it serves primarily to expose high-level generic functionality as well as a 8 | specific implementation for the port forwarding features. 9 | 10 | ## Usage 11 | 12 | #### type Client 13 | 14 | ```go 15 | type Client struct { 16 | Username, Password, Address string 17 | 18 | Path, Suffix, LoginEndpoint string 19 | } 20 | ``` 21 | 22 | A Client can interact with the EdgeOS REST API 23 | 24 | #### func NewClient 25 | 26 | ```go 27 | func NewClient(addr, username, password string) (*Client, error) 28 | ``` 29 | NewClient returns an initialized Client for interacting with an EdgeOS device 30 | 31 | #### func (*Client) Feature 32 | 33 | ```go 34 | func (c *Client) Feature(s Scenario) (Resp, error) 35 | ``` 36 | Feature takes an EdgeOS "Scenario" as an argument and returns a Resp 37 | representing the JSON returned by the API. 38 | 39 | #### func (*Client) FeatureFor 40 | 41 | ```go 42 | func (c *Client) FeatureFor(s Scenario, out interface{}) error 43 | ``` 44 | FeatureFor takes a scenario and a pointer to a struct. The JSON response will be 45 | deserialized into the `out` object, and any errors will be returned. 46 | 47 | #### func (*Client) Get 48 | 49 | ```go 50 | func (c *Client) Get() (Resp, error) 51 | ``` 52 | Get returns some standard configuration information from EdgeOS 53 | 54 | #### func (*Client) GetJSON 55 | 56 | ```go 57 | func (c *Client) GetJSON(endpoint string, data interface{}) (Resp, error) 58 | ``` 59 | GetJSON takes an endpoint and data for the POST body (or GET if `data` is nil) 60 | and returns the Resp type that contains the data response from the endpoint. 61 | 62 | #### func (*Client) JSONFor 63 | 64 | ```go 65 | func (c *Client) JSONFor(endpoint string, data, out interface{}) error 66 | ``` 67 | JSONFor is a high-level method that takes an endpoint, a post body, and a 68 | pointer to a struct into which the JSON should be decoded. 69 | 70 | #### func (*Client) Login 71 | 72 | ```go 73 | func (c *Client) Login() error 74 | ``` 75 | Login sets up an http session with the EdgeOS device using the supplied endpoint 76 | and credentials 77 | 78 | #### func (*Client) PortForwards 79 | 80 | ```go 81 | func (c *Client) PortForwards() (*FeatureResponse, error) 82 | ``` 83 | 84 | #### func (*Client) SetFeature 85 | 86 | ```go 87 | func (c *Client) SetFeature(s Scenario, data interface{}) (Resp, error) 88 | ``` 89 | SetFeature allows users to programmatically update features 90 | 91 | #### func (*Client) SetFeatureFor 92 | 93 | ```go 94 | func (c *Client) SetFeatureFor(s Scenario, data interface{}, out interface{}) error 95 | ``` 96 | SetFeatureFor takes a "Scenario", some data to send, and a pointer to an 97 | interface into which the JSON response will be decoded. 98 | 99 | #### type Config 100 | 101 | ```go 102 | type Config struct { 103 | Success bool `json:"success"` 104 | GET struct { 105 | Firewall struct { 106 | AllPing string `json:"all-ping"` 107 | BroadcastPing string `json:"broadcast-ping"` 108 | Ipv6ReceiveRedirects string `json:"ipv6-receive-redirects"` 109 | Ipv6SrcRoute string `json:"ipv6-src-route"` 110 | IPSrcRoute string `json:"ip-src-route"` 111 | LogMartians string `json:"log-martians"` 112 | Name struct { 113 | LANLOCAL struct { 114 | DefaultAction string `json:"default-action"` 115 | Description string `json:"description"` 116 | Rule struct { 117 | Num1 struct { 118 | Action string `json:"action"` 119 | Description string `json:"description"` 120 | Destination struct { 121 | Group struct { 122 | AddressGroup string `json:"address-group"` 123 | } `json:"group"` 124 | } `json:"destination"` 125 | Log string `json:"log"` 126 | Protocol string `json:"protocol"` 127 | Source struct { 128 | Group interface{} `json:"group"` 129 | } `json:"source"` 130 | State struct { 131 | Established string `json:"established"` 132 | Invalid string `json:"invalid"` 133 | New string `json:"new"` 134 | Related string `json:"related"` 135 | } `json:"state"` 136 | } `json:"1"` 137 | Num2 struct { 138 | Action string `json:"action"` 139 | Description string `json:"description"` 140 | Destination struct { 141 | Group struct { 142 | AddressGroup string `json:"address-group"` 143 | } `json:"group"` 144 | } `json:"destination"` 145 | Log string `json:"log"` 146 | Protocol string `json:"protocol"` 147 | Source struct { 148 | Group interface{} `json:"group"` 149 | } `json:"source"` 150 | State struct { 151 | Established string `json:"established"` 152 | Invalid string `json:"invalid"` 153 | New string `json:"new"` 154 | Related string `json:"related"` 155 | } `json:"state"` 156 | } `json:"2"` 157 | } `json:"rule"` 158 | } `json:"LAN_LOCAL"` 159 | WANIN struct { 160 | DefaultAction string `json:"default-action"` 161 | Description string `json:"description"` 162 | EnableDefaultLog interface{} `json:"enable-default-log"` 163 | Rule struct { 164 | Num20 struct { 165 | Action string `json:"action"` 166 | Description string `json:"description"` 167 | Log string `json:"log"` 168 | Protocol string `json:"protocol"` 169 | State struct { 170 | Established string `json:"established"` 171 | Invalid string `json:"invalid"` 172 | New string `json:"new"` 173 | Related string `json:"related"` 174 | } `json:"state"` 175 | } `json:"20"` 176 | Num30 struct { 177 | Action string `json:"action"` 178 | Description string `json:"description"` 179 | Log string `json:"log"` 180 | Protocol string `json:"protocol"` 181 | Source struct { 182 | Group interface{} `json:"group"` 183 | } `json:"source"` 184 | State struct { 185 | Established string `json:"established"` 186 | Invalid string `json:"invalid"` 187 | New string `json:"new"` 188 | Related string `json:"related"` 189 | } `json:"state"` 190 | } `json:"30"` 191 | } `json:"rule"` 192 | } `json:"WAN_IN"` 193 | WANLOCAL struct { 194 | DefaultAction string `json:"default-action"` 195 | Description string `json:"description"` 196 | EnableDefaultLog interface{} `json:"enable-default-log"` 197 | Rule struct { 198 | Num10 struct { 199 | Action string `json:"action"` 200 | Description string `json:"description"` 201 | Log string `json:"log"` 202 | Protocol string `json:"protocol"` 203 | State struct { 204 | Established string `json:"established"` 205 | Invalid string `json:"invalid"` 206 | New string `json:"new"` 207 | Related string `json:"related"` 208 | } `json:"state"` 209 | } `json:"10"` 210 | Num20 struct { 211 | Action string `json:"action"` 212 | Description string `json:"description"` 213 | Destination struct { 214 | Group struct { 215 | AddressGroup string `json:"address-group"` 216 | } `json:"group"` 217 | } `json:"destination"` 218 | Log string `json:"log"` 219 | Protocol string `json:"protocol"` 220 | } `json:"20"` 221 | Num30 struct { 222 | Action string `json:"action"` 223 | Description string `json:"description"` 224 | Log string `json:"log"` 225 | Protocol string `json:"protocol"` 226 | Source struct { 227 | Group interface{} `json:"group"` 228 | } `json:"source"` 229 | State struct { 230 | Established string `json:"established"` 231 | Invalid string `json:"invalid"` 232 | New string `json:"new"` 233 | Related string `json:"related"` 234 | } `json:"state"` 235 | } `json:"30"` 236 | } `json:"rule"` 237 | } `json:"WAN_LOCAL"` 238 | } `json:"name"` 239 | Options interface{} `json:"options"` 240 | ReceiveRedirects string `json:"receive-redirects"` 241 | SendRedirects string `json:"send-redirects"` 242 | SourceValidation string `json:"source-validation"` 243 | SynCookies string `json:"syn-cookies"` 244 | } `json:"firewall"` 245 | Interfaces struct { 246 | Ethernet struct { 247 | Eth0 struct { 248 | Address []string `json:"address"` 249 | Description string `json:"description"` 250 | Duplex string `json:"duplex"` 251 | Firewall struct { 252 | In struct { 253 | Name string `json:"name"` 254 | } `json:"in"` 255 | Local struct { 256 | Name string `json:"name"` 257 | } `json:"local"` 258 | } `json:"firewall"` 259 | Speed string `json:"speed"` 260 | } `json:"eth0"` 261 | Eth1 struct { 262 | Address []string `json:"address"` 263 | Description string `json:"description"` 264 | Duplex string `json:"duplex"` 265 | Firewall struct { 266 | Local struct { 267 | Name string `json:"name"` 268 | } `json:"local"` 269 | } `json:"firewall"` 270 | Speed string `json:"speed"` 271 | } `json:"eth1"` 272 | Eth2 struct { 273 | Duplex string `json:"duplex"` 274 | Speed string `json:"speed"` 275 | } `json:"eth2"` 276 | } `json:"ethernet"` 277 | Loopback struct { 278 | Lo interface{} `json:"lo"` 279 | } `json:"loopback"` 280 | } `json:"interfaces"` 281 | Service struct { 282 | DhcpServer struct { 283 | Disabled string `json:"disabled"` 284 | HostfileUpdate string `json:"hostfile-update"` 285 | SharedNetworkName struct { 286 | Internal struct { 287 | Authoritative string `json:"authoritative"` 288 | Subnet struct { 289 | One9216816122 struct { 290 | DefaultRouter string `json:"default-router"` 291 | DNSServer []string `json:"dns-server"` 292 | DomainName string `json:"domain-name"` 293 | Lease string `json:"lease"` 294 | Start struct { 295 | One9216816100 struct { 296 | Stop string `json:"stop"` 297 | } `json:"192.168.16.100"` 298 | } `json:"start"` 299 | StaticMapping struct { 300 | BRN30055CC581BE struct { 301 | IPAddress string `json:"ip-address"` 302 | MacAddress string `json:"mac-address"` 303 | } `json:"BRN30055CC581BE"` 304 | R6300V2 struct { 305 | IPAddress string `json:"ip-address"` 306 | MacAddress string `json:"mac-address"` 307 | } `json:"R6300v2"` 308 | Astuart struct { 309 | IPAddress string `json:"ip-address"` 310 | MacAddress string `json:"mac-address"` 311 | } `json:"astuart"` 312 | } `json:"static-mapping"` 313 | } `json:"192.168.16.1/22"` 314 | } `json:"subnet"` 315 | } `json:"Internal"` 316 | } `json:"shared-network-name"` 317 | UseDnsmasq string `json:"use-dnsmasq"` 318 | } `json:"dhcp-server"` 319 | DNS struct { 320 | Forwarding struct { 321 | CacheSize string `json:"cache-size"` 322 | ListenOn []string `json:"listen-on"` 323 | } `json:"forwarding"` 324 | } `json:"dns"` 325 | Gui struct { 326 | HTTPPort string `json:"http-port"` 327 | HTTPSPort string `json:"https-port"` 328 | OlderCiphers string `json:"older-ciphers"` 329 | } `json:"gui"` 330 | Nat struct { 331 | Rule struct { 332 | Num5000 struct { 333 | Description string `json:"description"` 334 | Log string `json:"log"` 335 | OutboundInterface string `json:"outbound-interface"` 336 | Protocol string `json:"protocol"` 337 | Source struct { 338 | Group interface{} `json:"group"` 339 | } `json:"source"` 340 | Type string `json:"type"` 341 | } `json:"5000"` 342 | } `json:"rule"` 343 | } `json:"nat"` 344 | Snmp struct { 345 | Community struct { 346 | Public struct { 347 | Authorization string `json:"authorization"` 348 | Network []string `json:"network"` 349 | } `json:"public"` 350 | } `json:"community"` 351 | Contact string `json:"contact"` 352 | Location string `json:"location"` 353 | } `json:"snmp"` 354 | SSH struct { 355 | Port string `json:"port"` 356 | ProtocolVersion string `json:"protocol-version"` 357 | } `json:"ssh"` 358 | } `json:"service"` 359 | System struct { 360 | DomainName string `json:"domain-name"` 361 | HostName string `json:"host-name"` 362 | Login struct { 363 | User struct { 364 | Andrew struct { 365 | Authentication struct { 366 | EncryptedPassword string `json:"encrypted-password"` 367 | PlaintextPassword string `json:"plaintext-password"` 368 | PublicKeys struct { 369 | AndrewAstuart struct { 370 | Key string `json:"key"` 371 | Type string `json:"type"` 372 | } `json:"andrew@astuart"` 373 | AndrewDesktop struct { 374 | Key string `json:"key"` 375 | Type string `json:"type"` 376 | } `json:"andrew@desktop"` 377 | } `json:"public-keys"` 378 | } `json:"authentication"` 379 | FullName string `json:"full-name"` 380 | Level string `json:"level"` 381 | } `json:"andrew"` 382 | } `json:"user"` 383 | } `json:"login"` 384 | NameServer []string `json:"name-server"` 385 | Offload struct { 386 | Hwnat string `json:"hwnat"` 387 | Ipsec string `json:"ipsec"` 388 | Ipv4 struct { 389 | Forwarding string `json:"forwarding"` 390 | } `json:"ipv4"` 391 | Ipv6 struct { 392 | Forwarding string `json:"forwarding"` 393 | } `json:"ipv6"` 394 | } `json:"offload"` 395 | Syslog struct { 396 | Global struct { 397 | Facility struct { 398 | All struct { 399 | Level string `json:"level"` 400 | } `json:"all"` 401 | Protocols struct { 402 | Level string `json:"level"` 403 | } `json:"protocols"` 404 | } `json:"facility"` 405 | } `json:"global"` 406 | Host struct { 407 | One9216816115140 struct { 408 | Facility struct { 409 | All struct { 410 | Level string `json:"level"` 411 | } `json:"all"` 412 | } `json:"facility"` 413 | } `json:"192.168.16.11:5140"` 414 | LogsAstuartCo5140 struct { 415 | Facility struct { 416 | All struct { 417 | Level string `json:"level"` 418 | } `json:"all"` 419 | } `json:"facility"` 420 | } `json:"logs.astuart.co:5140"` 421 | } `json:"host"` 422 | } `json:"syslog"` 423 | TimeZone string `json:"time-zone"` 424 | TrafficAnalysis struct { 425 | Dpi string `json:"dpi"` 426 | Export string `json:"export"` 427 | } `json:"traffic-analysis"` 428 | } `json:"system"` 429 | Vpn interface{} `json:"vpn"` 430 | Protocols struct { 431 | Static struct { 432 | Route struct { 433 | One7231254124 struct { 434 | NextHop struct { 435 | One921681612 struct { 436 | Description string `json:"description"` 437 | Distance string `json:"distance"` 438 | } `json:"192.168.16.12"` 439 | } `json:"next-hop"` 440 | } `json:"172.31.254.1/24"` 441 | One7231255124 struct { 442 | NextHop struct { 443 | One921681611 struct { 444 | Description string `json:"description"` 445 | Distance string `json:"distance"` 446 | } `json:"192.168.16.11"` 447 | } `json:"next-hop"` 448 | } `json:"172.31.255.1/24"` 449 | } `json:"route"` 450 | } `json:"static"` 451 | } `json:"protocols"` 452 | TrafficControl interface{} `json:"traffic-control"` 453 | } `json:"GET"` 454 | SESSIONID string `json:"SESSION_ID"` 455 | } 456 | ``` 457 | 458 | 459 | #### type Feature 460 | 461 | ```go 462 | type Feature struct { 463 | Data PortForwards 464 | // Data interface{} 465 | Definition interface{} 466 | 467 | Deletable string 468 | Success string 469 | } 470 | ``` 471 | 472 | 473 | #### type FeatureResponse 474 | 475 | ```go 476 | type FeatureResponse struct { 477 | Response 478 | Feature Feature `json:"FEATURE"` 479 | } 480 | ``` 481 | 482 | FeatureResponse encapsulates the Response metadata and the Feature 483 | 484 | #### type LanConfig 485 | 486 | ```go 487 | type LanConfig map[string]string 488 | ``` 489 | 490 | LanConfig is a simple string map that encapsulates the LAN configuration data. 491 | 492 | #### type PortForward 493 | 494 | ```go 495 | type PortForward struct { 496 | PortFrom string `json:"original-port"` 497 | PortTo string `json:"forward-to-port"` 498 | IPTo string `json:"forward-to-address"` 499 | Protocol string `json:"protocol"` 500 | Description string `json:"description"` 501 | } 502 | ``` 503 | 504 | PortForward is a struct that represents a port forwarding rule 505 | 506 | #### type PortForwards 507 | 508 | ```go 509 | type PortForwards struct { 510 | AutoFirewall string `json:"auto-firewall"` 511 | HairpinNAT string `json:"hairpin-nat"` 512 | WAN string `json:"wan"` 513 | Lans []LanConfig `json:"lans-config"` 514 | Rules []PortForward `json:"rules-config"` 515 | } 516 | ``` 517 | 518 | PortForwards is a struct that represents the response from the API for with the 519 | list of port forwards, etc. 520 | 521 | #### type Resp 522 | 523 | ```go 524 | type Resp map[string]interface{} 525 | ``` 526 | 527 | Resp is the basic response type for the EdgeOS API. Higher-level methods will 528 | tend to skip this type and return strongly-typed objects for specific endpoints. 529 | 530 | #### type Response 531 | 532 | ```go 533 | type Response struct { 534 | Success bool `json:"success"` 535 | Error string `json:"error"` 536 | } 537 | ``` 538 | 539 | Response is a struct (intended for embedding) that represents response metadata 540 | returned by the EdgeOS API. 541 | 542 | #### type Scenario 543 | 544 | ```go 545 | type Scenario string 546 | ``` 547 | 548 | Scenario is just a string type to encourage the use of internal constants. 549 | 550 | ```go 551 | const ( 552 | PortForwarding Scenario = ".Port_Forwarding" 553 | ) 554 | ``` 555 | Common feature endpoints 556 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module astuart.co/edgeos-rest 2 | 3 | require ( 4 | github.com/davecgh/go-spew v1.1.1 // indirect 5 | github.com/pmezard/go-difflib v1.0.0 // indirect 6 | github.com/stretchr/testify v1.2.2 7 | ) 8 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 4 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 5 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 6 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 7 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "astuart.co/edgeos-rest/pkg/edgeos" 8 | ) 9 | 10 | func main() { 11 | c, err := edgeos.NewClient(os.Getenv("ERLITE_ADDR"), os.Getenv("ERLITE_USER"), os.Getenv("ERLITE_PASS")) 12 | if err != nil { 13 | log.Fatal(err) 14 | } 15 | 16 | if err := c.Login(); err != nil { 17 | log.Fatal(err) 18 | } 19 | 20 | feat, err := c.Feature(edgeos.PortForwarding) 21 | if err != nil { 22 | log.Fatal(err) 23 | } 24 | 25 | d := feat["data"].(map[string]interface{})["rules-config"].([]interface{}) 26 | 27 | d = d[:len(d)-1] 28 | 29 | feat["data"].(map[string]interface{})["rules-config"] = d 30 | 31 | log.Println(feat["data"]) 32 | log.Println() 33 | log.Println() 34 | 35 | // log.Println(c.SetFeature(edgeos.PortForwarding, feat["data"])) 36 | } 37 | -------------------------------------------------------------------------------- /pkg/edgeos/client.go: -------------------------------------------------------------------------------- 1 | package edgeos 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "io" 8 | "net/http" 9 | "net/http/cookiejar" 10 | "net/url" 11 | "os" 12 | ) 13 | 14 | // Scenario is just a string type to encourage the use of internal constants. 15 | type Scenario string 16 | 17 | // Common feature endpoints 18 | const ( 19 | PortForwarding Scenario = ".Port_Forwarding" 20 | ) 21 | 22 | // A Client can interact with the EdgeOS REST API 23 | type Client struct { 24 | Username, Password, Address string 25 | 26 | Path, Suffix, LoginEndpoint string 27 | 28 | cli *http.Client 29 | } 30 | 31 | // Endpoint provides a quick way to get a formatted string for an edgeos 32 | // endpoint. 33 | func (c *Client) Endpoint(s string) string { 34 | return fmt.Sprintf("%s/%s/%s%s", c.Address, c.Path, s, c.Suffix) 35 | } 36 | 37 | // Login sets up an http session with the EdgeOS device using the supplied 38 | // endpoint and credentials 39 | func (c *Client) Login() error { 40 | v := url.Values{ 41 | "username": []string{c.Username}, 42 | "password": []string{c.Password}, 43 | } 44 | 45 | res, err := c.cli.PostForm(c.Address+"/"+c.LoginEndpoint, v) 46 | if err != nil { 47 | return err 48 | } 49 | defer res.Body.Close() 50 | 51 | return nil 52 | } 53 | 54 | // Resp is the basic response type for the EdgeOS API. Higher-level methods 55 | // will tend to skip this type and return strongly-typed objects for specific 56 | // endpoints. 57 | type Resp map[string]interface{} 58 | 59 | // GetJSON takes an endpoint and data for the POST body (or GET if `data` is nil) and 60 | // returns the Resp type that contains the data response from the endpoint. 61 | func (c *Client) GetJSON(endpoint string, data interface{}) (Resp, error) { 62 | var m map[string]interface{} 63 | 64 | err := c.JSONFor(endpoint, data, &m) 65 | 66 | return m, err 67 | } 68 | 69 | // DoFor wraps the http client's Do method for callers, writing the json to 70 | // `out` and returning any errors encountered. 71 | func (c *Client) DoFor(req *http.Request, out interface{}) error { 72 | res, err := c.cli.Do(req) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | defer res.Body.Close() 78 | return json.NewDecoder(io.TeeReader(res.Body, os.Stdout)).Decode(out) 79 | } 80 | 81 | // JSONFor is a high-level method that takes an endpoint, a post body, and a 82 | // pointer to a struct into which the JSON should be decoded. 83 | func (c *Client) JSONFor(endpoint string, data, out interface{}) error { 84 | var ( 85 | res *http.Response 86 | err error 87 | ) 88 | if data == nil { 89 | res, err = c.cli.Get(c.Endpoint(endpoint)) 90 | } else { 91 | bs, _ := json.Marshal(map[string]interface{}{"data": data}) 92 | res, err = c.cli.Post(c.Endpoint(endpoint), "application/json", bytes.NewReader(bs)) 93 | } 94 | 95 | if err != nil { 96 | return err 97 | } 98 | defer res.Body.Close() 99 | 100 | return json.NewDecoder(res.Body).Decode(out) 101 | } 102 | 103 | // Get returns some standard configuration information from EdgeOS 104 | func (c *Client) Get() (Resp, error) { 105 | return c.GetJSON("get", nil) 106 | } 107 | 108 | // Feature takes an EdgeOS "Scenario" as an argument and returns a Resp 109 | // representing the JSON returned by the API. 110 | func (c *Client) Feature(s Scenario) (Resp, error) { 111 | f, err := c.GetJSON("feature", map[string]string{ 112 | "action": "load", 113 | "scenario": string(s), 114 | }) 115 | 116 | if err != nil { 117 | return Resp{}, err 118 | } 119 | 120 | return Resp(f["FEATURE"].(map[string]interface{})), nil 121 | } 122 | 123 | // FeatureFor takes a scenario and a pointer to a struct. The JSON response 124 | // will be deserialized into the `out` object, and any errors will be returned. 125 | func (c *Client) FeatureFor(s Scenario, out interface{}) error { 126 | return c.JSONFor("feature", map[string]string{ 127 | "action": "load", 128 | "scenario": string(s), 129 | }, out) 130 | } 131 | 132 | // SetFeature allows users to programmatically update features 133 | func (c *Client) SetFeature(s Scenario, data interface{}) (Resp, error) { 134 | f, err := c.GetJSON("feature", map[string]interface{}{ 135 | "action": "apply", 136 | "apply": data, 137 | "scenario": string(s), 138 | }) 139 | if err != nil { 140 | return Resp{}, err 141 | } 142 | 143 | return Resp(f["FEATURE"].(map[string]interface{})), nil 144 | } 145 | 146 | // SetFeatureFor takes a "Scenario", some data to send, and a pointer to an 147 | // interface into which the JSON response will be decoded. 148 | func (c *Client) SetFeatureFor(s Scenario, data interface{}, out interface{}) error { 149 | return c.JSONFor("feature", map[string]interface{}{ 150 | "action": "apply", 151 | "apply": data, 152 | "scenario": string(s), 153 | }, out) 154 | } 155 | 156 | // NewClient returns an initialized Client for interacting with an EdgeOS device 157 | func NewClient(addr, username, password string) (*Client, error) { 158 | jar, err := cookiejar.New(nil) 159 | if err != nil { 160 | return nil, err 161 | } 162 | 163 | c := &Client{ 164 | Username: username, 165 | Password: password, 166 | Path: "api/edge", 167 | Suffix: ".json", 168 | Address: addr, 169 | cli: &http.Client{ 170 | Transport: &csrfTransport{ 171 | Referrer: addr, 172 | }, 173 | Jar: jar, 174 | }, 175 | } 176 | return c, nil 177 | } 178 | -------------------------------------------------------------------------------- /pkg/edgeos/config.go: -------------------------------------------------------------------------------- 1 | package edgeos 2 | 3 | type Config struct { 4 | Success bool `json:"success"` 5 | GET struct { 6 | Firewall struct { 7 | AllPing string `json:"all-ping"` 8 | BroadcastPing string `json:"broadcast-ping"` 9 | Ipv6ReceiveRedirects string `json:"ipv6-receive-redirects"` 10 | Ipv6SrcRoute string `json:"ipv6-src-route"` 11 | IPSrcRoute string `json:"ip-src-route"` 12 | LogMartians string `json:"log-martians"` 13 | Name struct { 14 | LANLOCAL struct { 15 | DefaultAction string `json:"default-action"` 16 | Description string `json:"description"` 17 | Rule struct { 18 | Num1 struct { 19 | Action string `json:"action"` 20 | Description string `json:"description"` 21 | Destination struct { 22 | Group struct { 23 | AddressGroup string `json:"address-group"` 24 | } `json:"group"` 25 | } `json:"destination"` 26 | Log string `json:"log"` 27 | Protocol string `json:"protocol"` 28 | Source struct { 29 | Group interface{} `json:"group"` 30 | } `json:"source"` 31 | State struct { 32 | Established string `json:"established"` 33 | Invalid string `json:"invalid"` 34 | New string `json:"new"` 35 | Related string `json:"related"` 36 | } `json:"state"` 37 | } `json:"1"` 38 | Num2 struct { 39 | Action string `json:"action"` 40 | Description string `json:"description"` 41 | Destination struct { 42 | Group struct { 43 | AddressGroup string `json:"address-group"` 44 | } `json:"group"` 45 | } `json:"destination"` 46 | Log string `json:"log"` 47 | Protocol string `json:"protocol"` 48 | Source struct { 49 | Group interface{} `json:"group"` 50 | } `json:"source"` 51 | State struct { 52 | Established string `json:"established"` 53 | Invalid string `json:"invalid"` 54 | New string `json:"new"` 55 | Related string `json:"related"` 56 | } `json:"state"` 57 | } `json:"2"` 58 | } `json:"rule"` 59 | } `json:"LAN_LOCAL"` 60 | WANIN struct { 61 | DefaultAction string `json:"default-action"` 62 | Description string `json:"description"` 63 | EnableDefaultLog interface{} `json:"enable-default-log"` 64 | Rule struct { 65 | Num20 struct { 66 | Action string `json:"action"` 67 | Description string `json:"description"` 68 | Log string `json:"log"` 69 | Protocol string `json:"protocol"` 70 | State struct { 71 | Established string `json:"established"` 72 | Invalid string `json:"invalid"` 73 | New string `json:"new"` 74 | Related string `json:"related"` 75 | } `json:"state"` 76 | } `json:"20"` 77 | Num30 struct { 78 | Action string `json:"action"` 79 | Description string `json:"description"` 80 | Log string `json:"log"` 81 | Protocol string `json:"protocol"` 82 | Source struct { 83 | Group interface{} `json:"group"` 84 | } `json:"source"` 85 | State struct { 86 | Established string `json:"established"` 87 | Invalid string `json:"invalid"` 88 | New string `json:"new"` 89 | Related string `json:"related"` 90 | } `json:"state"` 91 | } `json:"30"` 92 | } `json:"rule"` 93 | } `json:"WAN_IN"` 94 | WANLOCAL struct { 95 | DefaultAction string `json:"default-action"` 96 | Description string `json:"description"` 97 | EnableDefaultLog interface{} `json:"enable-default-log"` 98 | Rule struct { 99 | Num10 struct { 100 | Action string `json:"action"` 101 | Description string `json:"description"` 102 | Log string `json:"log"` 103 | Protocol string `json:"protocol"` 104 | State struct { 105 | Established string `json:"established"` 106 | Invalid string `json:"invalid"` 107 | New string `json:"new"` 108 | Related string `json:"related"` 109 | } `json:"state"` 110 | } `json:"10"` 111 | Num20 struct { 112 | Action string `json:"action"` 113 | Description string `json:"description"` 114 | Destination struct { 115 | Group struct { 116 | AddressGroup string `json:"address-group"` 117 | } `json:"group"` 118 | } `json:"destination"` 119 | Log string `json:"log"` 120 | Protocol string `json:"protocol"` 121 | } `json:"20"` 122 | Num30 struct { 123 | Action string `json:"action"` 124 | Description string `json:"description"` 125 | Log string `json:"log"` 126 | Protocol string `json:"protocol"` 127 | Source struct { 128 | Group interface{} `json:"group"` 129 | } `json:"source"` 130 | State struct { 131 | Established string `json:"established"` 132 | Invalid string `json:"invalid"` 133 | New string `json:"new"` 134 | Related string `json:"related"` 135 | } `json:"state"` 136 | } `json:"30"` 137 | } `json:"rule"` 138 | } `json:"WAN_LOCAL"` 139 | } `json:"name"` 140 | Options interface{} `json:"options"` 141 | ReceiveRedirects string `json:"receive-redirects"` 142 | SendRedirects string `json:"send-redirects"` 143 | SourceValidation string `json:"source-validation"` 144 | SynCookies string `json:"syn-cookies"` 145 | } `json:"firewall"` 146 | Interfaces struct { 147 | Ethernet struct { 148 | Eth0 struct { 149 | Address []string `json:"address"` 150 | Description string `json:"description"` 151 | Duplex string `json:"duplex"` 152 | Firewall struct { 153 | In struct { 154 | Name string `json:"name"` 155 | } `json:"in"` 156 | Local struct { 157 | Name string `json:"name"` 158 | } `json:"local"` 159 | } `json:"firewall"` 160 | Speed string `json:"speed"` 161 | } `json:"eth0"` 162 | Eth1 struct { 163 | Address []string `json:"address"` 164 | Description string `json:"description"` 165 | Duplex string `json:"duplex"` 166 | Firewall struct { 167 | Local struct { 168 | Name string `json:"name"` 169 | } `json:"local"` 170 | } `json:"firewall"` 171 | Speed string `json:"speed"` 172 | } `json:"eth1"` 173 | Eth2 struct { 174 | Duplex string `json:"duplex"` 175 | Speed string `json:"speed"` 176 | } `json:"eth2"` 177 | } `json:"ethernet"` 178 | Loopback struct { 179 | Lo interface{} `json:"lo"` 180 | } `json:"loopback"` 181 | } `json:"interfaces"` 182 | Service struct { 183 | DhcpServer struct { 184 | Disabled string `json:"disabled"` 185 | HostfileUpdate string `json:"hostfile-update"` 186 | SharedNetworkName struct { 187 | Internal struct { 188 | Authoritative string `json:"authoritative"` 189 | Subnet struct { 190 | One9216816122 struct { 191 | DefaultRouter string `json:"default-router"` 192 | DNSServer []string `json:"dns-server"` 193 | DomainName string `json:"domain-name"` 194 | Lease string `json:"lease"` 195 | Start struct { 196 | One9216816100 struct { 197 | Stop string `json:"stop"` 198 | } `json:"192.168.16.100"` 199 | } `json:"start"` 200 | StaticMapping struct { 201 | BRN30055CC581BE struct { 202 | IPAddress string `json:"ip-address"` 203 | MacAddress string `json:"mac-address"` 204 | } `json:"BRN30055CC581BE"` 205 | R6300V2 struct { 206 | IPAddress string `json:"ip-address"` 207 | MacAddress string `json:"mac-address"` 208 | } `json:"R6300v2"` 209 | Astuart struct { 210 | IPAddress string `json:"ip-address"` 211 | MacAddress string `json:"mac-address"` 212 | } `json:"astuart"` 213 | } `json:"static-mapping"` 214 | } `json:"192.168.16.1/22"` 215 | } `json:"subnet"` 216 | } `json:"Internal"` 217 | } `json:"shared-network-name"` 218 | UseDnsmasq string `json:"use-dnsmasq"` 219 | } `json:"dhcp-server"` 220 | DNS struct { 221 | Forwarding struct { 222 | CacheSize string `json:"cache-size"` 223 | ListenOn []string `json:"listen-on"` 224 | } `json:"forwarding"` 225 | } `json:"dns"` 226 | Gui struct { 227 | HTTPPort string `json:"http-port"` 228 | HTTPSPort string `json:"https-port"` 229 | OlderCiphers string `json:"older-ciphers"` 230 | } `json:"gui"` 231 | Nat struct { 232 | Rule struct { 233 | Num5000 struct { 234 | Description string `json:"description"` 235 | Log string `json:"log"` 236 | OutboundInterface string `json:"outbound-interface"` 237 | Protocol string `json:"protocol"` 238 | Source struct { 239 | Group interface{} `json:"group"` 240 | } `json:"source"` 241 | Type string `json:"type"` 242 | } `json:"5000"` 243 | } `json:"rule"` 244 | } `json:"nat"` 245 | Snmp struct { 246 | Community struct { 247 | Public struct { 248 | Authorization string `json:"authorization"` 249 | Network []string `json:"network"` 250 | } `json:"public"` 251 | } `json:"community"` 252 | Contact string `json:"contact"` 253 | Location string `json:"location"` 254 | } `json:"snmp"` 255 | SSH struct { 256 | Port string `json:"port"` 257 | ProtocolVersion string `json:"protocol-version"` 258 | } `json:"ssh"` 259 | } `json:"service"` 260 | System struct { 261 | DomainName string `json:"domain-name"` 262 | HostName string `json:"host-name"` 263 | Login struct { 264 | User struct { 265 | Andrew struct { 266 | Authentication struct { 267 | EncryptedPassword string `json:"encrypted-password"` 268 | PlaintextPassword string `json:"plaintext-password"` 269 | PublicKeys struct { 270 | AndrewAstuart struct { 271 | Key string `json:"key"` 272 | Type string `json:"type"` 273 | } `json:"andrew@astuart"` 274 | AndrewDesktop struct { 275 | Key string `json:"key"` 276 | Type string `json:"type"` 277 | } `json:"andrew@desktop"` 278 | } `json:"public-keys"` 279 | } `json:"authentication"` 280 | FullName string `json:"full-name"` 281 | Level string `json:"level"` 282 | } `json:"andrew"` 283 | } `json:"user"` 284 | } `json:"login"` 285 | NameServer []string `json:"name-server"` 286 | Offload struct { 287 | Hwnat string `json:"hwnat"` 288 | Ipsec string `json:"ipsec"` 289 | Ipv4 struct { 290 | Forwarding string `json:"forwarding"` 291 | } `json:"ipv4"` 292 | Ipv6 struct { 293 | Forwarding string `json:"forwarding"` 294 | } `json:"ipv6"` 295 | } `json:"offload"` 296 | Syslog struct { 297 | Global struct { 298 | Facility struct { 299 | All struct { 300 | Level string `json:"level"` 301 | } `json:"all"` 302 | Protocols struct { 303 | Level string `json:"level"` 304 | } `json:"protocols"` 305 | } `json:"facility"` 306 | } `json:"global"` 307 | Host struct { 308 | One9216816115140 struct { 309 | Facility struct { 310 | All struct { 311 | Level string `json:"level"` 312 | } `json:"all"` 313 | } `json:"facility"` 314 | } `json:"192.168.16.11:5140"` 315 | LogsAstuartCo5140 struct { 316 | Facility struct { 317 | All struct { 318 | Level string `json:"level"` 319 | } `json:"all"` 320 | } `json:"facility"` 321 | } `json:"logs.astuart.co:5140"` 322 | } `json:"host"` 323 | } `json:"syslog"` 324 | TimeZone string `json:"time-zone"` 325 | TrafficAnalysis struct { 326 | Dpi string `json:"dpi"` 327 | Export string `json:"export"` 328 | } `json:"traffic-analysis"` 329 | } `json:"system"` 330 | Vpn interface{} `json:"vpn"` 331 | Protocols struct { 332 | Static struct { 333 | Route struct { 334 | One7231254124 struct { 335 | NextHop struct { 336 | One921681612 struct { 337 | Description string `json:"description"` 338 | Distance string `json:"distance"` 339 | } `json:"192.168.16.12"` 340 | } `json:"next-hop"` 341 | } `json:"172.31.254.1/24"` 342 | One7231255124 struct { 343 | NextHop struct { 344 | One921681611 struct { 345 | Description string `json:"description"` 346 | Distance string `json:"distance"` 347 | } `json:"192.168.16.11"` 348 | } `json:"next-hop"` 349 | } `json:"172.31.255.1/24"` 350 | } `json:"route"` 351 | } `json:"static"` 352 | } `json:"protocols"` 353 | TrafficControl interface{} `json:"traffic-control"` 354 | } `json:"GET"` 355 | SESSIONID string `json:"SESSION_ID"` 356 | } 357 | -------------------------------------------------------------------------------- /pkg/edgeos/csrf.go: -------------------------------------------------------------------------------- 1 | package edgeos 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | ) 9 | 10 | // type rtf func(*http.Request) (*http.Response, error) 11 | type csrfTransport struct { 12 | Referrer string 13 | CSRF string 14 | Debug bool 15 | 16 | RoundTripper http.RoundTripper 17 | } 18 | 19 | func (r *csrfTransport) RoundTrip(req *http.Request) (*http.Response, error) { 20 | if r.RoundTripper == nil { 21 | r.RoundTripper = &http.Transport{ 22 | TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 23 | } 24 | } 25 | 26 | if req.Header.Get("Content-Type") == "" { 27 | req.Header.Set("Accept", "application/json") 28 | req.Header.Set("Content-Type", "application/json") 29 | req.Header.Set("X-Requested-With", "XMLHttpRequest") 30 | } 31 | 32 | // req.Header.Set("Origin", "https://www.hackerrank.com") 33 | req.Header.Set("Referer", r.Referrer+"/") 34 | 35 | if r.CSRF != "" { 36 | req.Header.Set("X-CSRF-Token", r.CSRF) 37 | } 38 | 39 | res, err := r.RoundTripper.RoundTrip(req) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | if res.Cookies() != nil { 45 | for _, cookie := range res.Cookies() { 46 | if cookie.Name == "X-CSRF-TOKEN" { 47 | r.CSRF = cookie.Value 48 | } 49 | } 50 | } 51 | 52 | if r.Debug { 53 | fmt.Println("Req") 54 | req.Write(os.Stdout) 55 | fmt.Println() 56 | 57 | fmt.Println("Res") 58 | res.Write(os.Stdout) 59 | fmt.Println() 60 | } 61 | 62 | return res, err 63 | } 64 | -------------------------------------------------------------------------------- /pkg/edgeos/doc.go: -------------------------------------------------------------------------------- 1 | // Package edgeos provides a Go client for interfacing with Ubiquiti EdgeOS 2 | // devices. It has been developed and tested against the ERLite running v1.9.1 3 | // firmware. Thus far, it serves primarily to expose high-level generic 4 | // functionality as well as a specific implementation for the port forwarding 5 | // features. 6 | package edgeos 7 | -------------------------------------------------------------------------------- /pkg/edgeos/port-forwarding.go: -------------------------------------------------------------------------------- 1 | package edgeos 2 | 3 | // PortForward is a struct that represents a port forwarding rule 4 | type PortForward struct { 5 | PortFrom string `json:"original-port"` 6 | PortTo string `json:"forward-to-port"` 7 | IPTo string `json:"forward-to-address"` 8 | Protocol string `json:"protocol"` 9 | Description string `json:"description"` 10 | } 11 | 12 | // PortForwards is a struct that represents the response from the API for with 13 | // the list of port forwards, etc. 14 | type PortForwards struct { 15 | AutoFirewall string `json:"auto-firewall"` 16 | HairpinNAT string `json:"hairpin-nat"` 17 | WAN string `json:"wan"` 18 | Lans []LanConfig `json:"lans-config"` 19 | Rules []PortForward `json:"rules-config"` 20 | } 21 | 22 | // LanConfig is a simple string map that encapsulates the LAN configuration 23 | // data. 24 | type LanConfig map[string]string 25 | 26 | // Response is a struct (intended for embedding) that represents response 27 | // metadata returned by the EdgeOS API. 28 | type Response struct { 29 | Success bool `json:"success"` 30 | Error string `json:"error"` 31 | } 32 | 33 | // FeatureResponse encapsulates the Response metadata and the Feature 34 | type FeatureResponse struct { 35 | Response 36 | Feature Feature `json:"FEATURE"` 37 | } 38 | 39 | type Feature struct { 40 | Data PortForwards 41 | // Data interface{} 42 | Definition interface{} 43 | 44 | Deletable string 45 | Success string 46 | } 47 | 48 | func (c *Client) PortForwards() (*FeatureResponse, error) { 49 | res := &FeatureResponse{} 50 | err := c.FeatureFor(PortForwarding, res) 51 | return res, err 52 | } 53 | -------------------------------------------------------------------------------- /pkg/edgeos/port-forwarding_test.go: -------------------------------------------------------------------------------- 1 | package edgeos 2 | 3 | import ( 4 | "encoding/json" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | const testJSON = `{"success":true,"FEATURE":{"data":{"wan":"eth0","hairpin-nat":"true","auto-firewall":"true","lans-config":[{"lan":"eth1"}],"rules-config":[{"description":"DNS","original-port":"53","protocol":"tcp_udp","forward-to-address":"192.168.16.5","forward-to-port":"53"},{"description":"OpenVPN Desktop","original-port":"1195","protocol":"udp","forward-to-address":"192.168.16.12","forward-to-port":"1195"},{"description":"Desk SSH","original-port":"2223","protocol":"tcp_udp","forward-to-address":"192.168.16.12","forward-to-port":"22"},{"description":"Vault","original-port":"8200","protocol":"tcp_udp","forward-to-address":"192.168.16.16","forward-to-port":"8200"},{"description":"OpenVPN HTPC","original-port":"1194","protocol":"udp","forward-to-address":"192.168.16.11","forward-to-port":"1194"},{"description":"SSH HTPC","original-port":"2222","protocol":"tcp_udp","forward-to-address":"192.168.16.11","forward-to-port":"22"},{"description":"Fire","original-port":"5431","protocol":"tcp_udp","forward-to-address":"192.168.16.12","forward-to-port":"5431"}]},"definition":{"lan":{"options":["eth2","eth0","eth1","imq0"],"other":"true"},"wan":{"options":["eth2","eth0","eth1","imq0"],"other":"true"}},"deletable":"1","success":"1"}}` 12 | 13 | func TestDecode(t *testing.T) { 14 | asrt := assert.New(t) 15 | var res FeatureResponse 16 | 17 | asrt.NoError(json.NewDecoder(strings.NewReader(testJSON)).Decode(&res)) 18 | 19 | asrt.True(res.Success) 20 | 21 | asrt.IsType(Feature{}, res.Feature) 22 | asrt.IsType(PortForwards{}, res.Feature.Data) 23 | 24 | asrt.Len(res.Feature.Data.Rules, 7) 25 | } 26 | -------------------------------------------------------------------------------- /pkg/edgeos/session.go: -------------------------------------------------------------------------------- 1 | package edgeos 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | ) 7 | 8 | type session struct { 9 | CStore map[string]string 10 | RTer http.RoundTripper 11 | } 12 | 13 | func (c *session) SetCookies(u *url.URL, cookies []*http.Cookie) { 14 | if c.CStore == nil { 15 | c.CStore = map[string]string{} 16 | } 17 | for _, ck := range cookies { 18 | c.CStore[ck.Name] = ck.Value 19 | } 20 | } 21 | 22 | //Cookies implements cookie store 23 | func (c *session) Cookies(u *url.URL) []*http.Cookie { 24 | if c.CStore == nil || len(c.CStore) == 0 { 25 | c.CStore = map[string]string{} 26 | } 27 | 28 | cookies := []*http.Cookie{} 29 | for k, v := range c.CStore { 30 | cookies = append(cookies, &http.Cookie{Name: k, Value: v}) 31 | } 32 | return cookies 33 | } 34 | --------------------------------------------------------------------------------