[
  {
    "path": ".gitignore",
    "content": ".env\n*.db\nbuild/\n\n# coverage\n*.html\n*.out"
  },
  {
    "path": "Dockerfile",
    "content": "FROM golang:alpine as builder\nRUN mkdir /build \nADD . /build/\nWORKDIR /build \nRUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags '-extldflags \"-static\"' ./cmd/main.go\nFROM scratch\nCOPY --from=builder /build/main /app/\nWORKDIR /app\nCMD [\"./main\"]"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2020 Prashant Gupta\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "COVER_PROFILE=cover.out\nCOVER_PROFILE_TEMP=cover.tmp.out\nCOVER_HTML=cover.html\n\n.PHONY: build $(COVER_PROFILE) $(COVER_HTML)\n\nall: coverage vet\n\ncoverage: $(COVER_HTML)\n\n$(COVER_HTML): $(COVER_PROFILE) ignoreFiles \n\tgo tool cover -html=$(COVER_PROFILE) -o $(COVER_HTML)\n\nignoreFiles:\n\tcat $(COVER_PROFILE_TEMP) | grep -v \"middleware.go\" | grep -v \"route.go\" > $(COVER_PROFILE)\n\n$(COVER_PROFILE):\n\tenv=local go test -v -failfast -race -coverprofile=$(COVER_PROFILE_TEMP) ./...\n\nvet:\n\tgo vet ./...\nstart-local: clean-db #for testing on your local system without firewalld\n\tenv=local go run cmd/main.go\nstart-server:\n\tgo run cmd/main.go\nbuild-linux: # example: make build-linux DB_PATH=/dir/to/db\n\tenv GOOS=linux GOARCH=amd64 go build -ldflags \"-X github.com/prashantgupta24/firewalld-rest/db.pathFromEnv=$(DB_PATH)\" -o build/firewalld-rest cmd/main.go\nlocal-build:\n\tgo build -ldflags \"-X github.com/prashantgupta24/firewalld-rest/db.pathFromEnv=$(DB_PATH)\" -o build/firewalld-rest cmd/main.go\ncopy: build-linux\n\tscp build/firewalld-rest root@<server>:/root/rest\nclean-db:\n\trm -f *.db\ntest:\n\tenv=local go test -v -failfast -race ./...\n"
  },
  {
    "path": "README.md",
    "content": "[![Mentioned in Awesome Go](https://awesome.re/mentioned-badge.svg)](https://github.com/avelino/awesome-go) [![Go Report Card](https://goreportcard.com/badge/github.com/prashantgupta24/firewalld-rest)](https://goreportcard.com/report/github.com/prashantgupta24/firewalld-rest) [![codecov](https://codecov.io/gh/prashantgupta24/firewalld-rest/branch/master/graph/badge.svg)](https://codecov.io/gh/prashantgupta24/firewalld-rest) [![version][version-badge]][releases]\n\n# Firewalld-rest\n\nA REST application to dynamically update firewalld rules on a linux server.\n\n_Firewalld is a firewall management tool for Linux operating systems._\n\n## Purpose\n\nIf you have seen this message when you login to your linux server:\n\n```\nThere were 534 failed login attempts since the last successful login.\n```\n\nThen this idea is for **you**.\n\nThe simple idea behind this is to have a completely isolated system, a system running Firewalld that does not permit SSH access to any IP address by default so there are no brute-force attacks. The only way to access the system is by communicating with a REST application running on the server through a valid request containing your public IP address.\n\nThe REST application validates your request (it checks for a valid JWT, covered later), and if the request is valid, it will add your IP to the firewalld rule for the public zone for SSH, which gives **only your IP** SSH access to the machine.\n\nOnce you are done using the machine, you can remove your IP interacting with the same REST application, and it changes rules in firewalld, shutting off SSH access and isolating the system again.\n\n## Comparison with fail2ban\n\nThis repo takes a proactive approach rather than a reactive approach taken by `fail2ban`. `fail2ban` dynamically alters the firewall rules to ban addresses that have unsuccessfully attempted to login a certain number of times. It is reactive - it allows people to try and login to the server, but bans those who are unsuccessful in doing so after a certain number of times. It is like appointing a guard (aka firewall) outside a locked building who checks for suspicious activity and the guard is told by `fail2ban` to ban anyone who tries to open the lock unsuccessfully many times.\n\nFirewalld-rest is more of a proactive approach. Let me explain.\n\n### Proactive approach\n\nBy using the approach presented in this repo, you still add a guard (aka firewall) like you did for fail2ban in front of your locked building (aka the server). But there are 2 main differences here:\n\n1. This guard is told to not let **anyone** come near the building by default, so that no one is ever close enough to the lock to try their keys. (This means that the default firewall rules are set up by default in such a way so that no one can even try to SSH to the server).\n2. You can talk to the guard (aka firewall) using this repo, and convince the guard to allow you near the building, provided you possess a certain key (an **RS256** type, covered later). (This means that using the REST interface provided by this repo, you proactively alter firewall rules to allow ONLY your IP to try and login to the server)\n\n`Note`: Once you are allowed through by the firewall, you still need to have the key to login to the server.\n\n> It is proactive - You proactively talk to the REST interface and alter the firewall rules to allow your IP to try and login.\n\n**TL;DR**\n\n| `fail2ban`                                                                                                                               | `firewalld-rest `                                                                                                                                                    |\n| ---------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `dynamically` alters firewall rules to ban IP addresses that have unsuccessfully attempted to login to server a certain number of times. | provides REST interface to `manually` alter firewall rules to allow ONLY your IP to try and login to server. No IP apart from yours can even try to login to server. |\n| `Reactive` - it alters firewall rules _after_ unsuccessfully attempts                                                                    | `Proactive` - you alter firewall rules _before_ trying to login to server                                                                                            |\n\n> Note: I am not saying one approach is better than the other. They are just different approaches to the same problem.\n\n## Table of Contents\n\n<!-- @import \"[TOC]\" {cmd=\"toc\" depthFrom=2 depthTo=6 orderedList=false} -->\n\n<!-- code_chunk_output -->\n\n- [Purpose](#purpose)\n- [Comparison with fail2ban](#comparison-with-fail2ban)\n  - [Proactive approach](#proactive-approach)\n- [Table of Contents](#table-of-contents)\n- [1. Pre-requisites](#1-pre-requisites)\n- [2. About the application](#2-about-the-application)\n  - [2.1 Firewall-cmd](#21-firewall-cmd)\n  - [2.2 Database](#22-database)\n  - [2.3 Authorization](#23-authorization)\n  - [2.4 Tests](#24-tests)\n- [3. How to install and use on server](#3-how-to-install-and-use-on-server)\n  - [3.1 Generate JWT](#31-generate-jwt)\n  - [3.2 Build the application](#32-build-the-application)\n  - [3.3 Copy binary file over to server](#33-copy-binary-file-over-to-server)\n  - [3.4 Remove SSH from public firewalld zone](#34-remove-ssh-from-public-firewalld-zone)\n  - [3.5 Expose the REST application](#35-expose-the-rest-application)\n    - [3.5.1 Single node cluster](#351-single-node-cluster)\n    - [3.5.2 Multi-node cluster](#352-multi-node-cluster)\n  - [3.6 Configure linux systemd service](#36-configure-linux-systemd-service)\n  - [3.7 Start and enable systemd service.](#37-start-and-enable-systemd-service)\n  - [3.8 IP JSON](#38-ip-json)\n  - [3.9 Interacting with the REST application](#39-interacting-with-the-rest-application)\n    - [3.9.1 Index page](#391-index-page)\n      - [Sample query](#sample-query)\n    - [3.9.2 Show all IPs](#392-show-all-ips)\n      - [Sample query](#sample-query-1)\n    - [3.9.3 Add new IP](#393-add-new-ip)\n      - [Sample query](#sample-query-2)\n    - [3.9.4 Show if IP is present](#394-show-if-ip-is-present)\n      - [Sample query](#sample-query-3)\n    - [3.9.5 Delete IP](#395-delete-ip)\n      - [Sample query](#sample-query-4)\n- [4. Helpful tips/links](#4-helpful-tipslinks)\n- [5. Commands for generating public/private key](#5-commands-for-generating-publicprivate-key)\n- [6. Possible enhancements](#6-possible-enhancements)\n\n<!-- /code_chunk_output -->\n\n## 1. Pre-requisites\n\nThis repo assumes you have:\n\n1. A linux server with `firewalld` installed.\n1. `root` access to the server. (without `root` access, the application will not be able to run the `firewall-cmd` commands needed to add the rule for SSH access)\n1. Some way of exposing the application externally (there are examples in this repo on how to use Kubernetes to expose the service)\n\n## 2. About the application\n\n### 2.1 Firewall-cmd\n\nFirewall-cmd is the command line client of the firewalld daemon. Through this, the REST application adds the rule specific to the IP address sent in the request.\n\nThe syntax of adding a rule for an IP address is:\n`firewall-cmd --permanent --zone=public --add-rich-rule='rule family=\"ipv4\" source address=\"10.xx.xx.xx/32\" port protocol=\"tcp\" port=\"22\" accept'`\n\nOnce the rule for the IP address has been added, the IP address is stored in a database (covered next). The database is just to keep track of all IPs that have rules created for them.\n\n### 2.2 Database\n\nThe database for the application stores the list of IP addresses that have rules created for them which allow SSH access for those IPs. Once you interact with the REST application and the application creates a firewalld rule specific to your IP address, then your IP address is stored in the database. It is important that the database is maintained during server restarts, otherwise there may be discrepancy between the IP addresses having firewalld rules and IP addresses stored in the database.\n\n> Note: Having an IP in the database does not mean that IP address will be given SSH access. The database is just a way to reference all the IPs with rules created in firewalld.\n\nThe application uses a file type database for now. The architecture of the code allows easy integration of any other type of databases. The interface in db.go is what is required to be fulfilled to introduce a new type of database.\n\n### 2.3 Authorization\n\nThe application uses `RS256` type algorithm to verify the incoming requests.\n\n> RS256 (RSA Signature with SHA-256) is an asymmetric algorithm, and it uses a public/private key pair: the identity provider has a private (secret) key used to generate the signature, and the consumer of the JWT gets a public key to validate the signature.\n\nThe public certificate is in this file [publicCert.go](https://github.com/prashantgupta24/firewalld-rest/blob/master/route/publicCert.go), which is something that will have to be changed before you can use it. (more information on how to create a new one later).\n\n### 2.4 Tests\n\nThe tests can be run using `make test`. The emphasis has been given to testing the handler functions and making sure that IPs get added and removed successfully from the database. I still have to figure out how to actually automate the tests for the firewalld rules (contributions are welcome!)\n\n## 3. How to install and use on server\n\n### 3.1 Generate JWT\n\nUpdate the file [publicCert.go](https://github.com/prashantgupta24/firewalld-rest/blob/master/route/publicCert.go) with your own `public cert` for which you have the private key.\n\nIf you want to create a new set, see the section on [generating your own public/private key](#5-commands-for-generating-publicprivate-key). Once you have your own public and private key pair, then after updating the file above, you can go to `jwt.io` and generate a valid JWT using `RS256 algorithm` (the payload doesn't matter). You will be using that JWT to make calls to the REST application, so keep the JWT safe.\n\n### 3.2 Build the application\n\nRun the command:\n\n```\nmake build-linux DB_PATH=/dir/to/db/\n```\n\nIt will create a binary under the build directory, called `firewalld-rest`. The `DB_PATH=/dir/to/keep/db` statement sets the path where the `.db` file will be saved **on the server**. It should be saved in a protected location such that it is not accidentally deleted on server restart or by any other user. A good place for it could be the same directory where you will copy the binary over to (in the next step). That way you will not forget where it is.\n\nIf `DB_PATH` variable is not set, the db file will be created by default under `/`. (_This happens because the binary is run by systemd. If we manually ran the binary file on the server, the db file would be created in the same directory._)\n\nOnce the binary is built, it should contain everything required to run the application on a linux based server.\n\n### 3.3 Copy binary file over to server\n\n```\nscp build/firewalld-rest root@<server>:/root/rest\n```\n\n_Note_: if you want to change the directory where you want to keep the binary, then make sure you edit the `firewalld-rest.service` file, as the `linux systemd service` definition example in this repo expects the location of the binary to be `/root/rest`.\n\n### 3.4 Remove SSH from public firewalld zone\n\nThis is to remove SSH access from the public zone, which will cease SSH access from everywhere.\n\nSSH into the server, and run the following command:\n\n```\nfirewall-cmd --zone=public --remove-service=ssh --permanent\n```\n\nthen reload (since we are using `--permanent`):\n\n```\nfirewall-cmd --reload\n```\n\nThis removes ssh access for everyone. This is where the application will come into play, and we enable access based on IP.\n\n**Confirmation for the step**:\n\n```\nfirewall-cmd --zone=public --list-all\n```\n\n_Notice the `ssh` service will not be listed in public zone anymore._\n\nAlso try SSH access into the server from another terminal. It should reject the attempt.\n\n### 3.5 Expose the REST application\n\nThe REST application can be exposed in a number of different ways, I have 2 examples on how it can be exposed:\n\n1. Using a `NodePort` kubernetes service ([link](https://github.com/prashantgupta24/firewalld-rest/blob/master/k8s/svc-nodeport.yaml))\n2. Using `ingress` along with a kubernetes service ([link](https://github.com/prashantgupta24/firewalld-rest/blob/master/k8s/ingress.yaml))\n\n#### 3.5.1 Single node cluster\n\nFor a single-node cluster, see the kubernetes service example [here](https://github.com/prashantgupta24/firewalld-rest/blob/master/k8s/svc-nodeport.yaml). The important thing to note is that we manually add the `Endpoints` resource for the service, which points to our node's private IP address and port `8080`.\n\nOnce deployed, your service might look like this:\n\n```\nkubernetes get svc\n\nexternal-rest | NodePort | 10.xx.xx.xx | 169.xx.xx.xx | 8080:31519/TCP\n```\n\nNow, you can interact with the application on:\n\n> 169.xx.xx.xx:31519/m1/\n\n_Note: Since there's only 1 node in the cluster, you will only ever use `/m1`. For more than 1 node, see the next section._\n\n#### 3.5.2 Multi-node cluster\n\nFor a multi-node cluster, an ingress resource would be highly beneficial.\n\nThe **first** step would be to create the kubernetes service in each individual node, using the example [here](https://github.com/prashantgupta24/firewalld-rest/blob/master/k8s/svc.yaml). The important thing to note is that we manually add the `Endpoints` resource for the service, which points to our node's private IP address and port `8080`.\n\nThe **second** step is the [ingress](https://github.com/prashantgupta24/firewalld-rest/blob/master/k8s/ingress.yaml) resource. It redirects different routes to different nodes in the cluster. For example, in the ingress file above,\n\nA request to `/m1` will be redirected to the `first` node, a request to `/m2` will be redirected to the `second` node, and so on. This will let you control each node's individual SSH access through a single endpoint.\n\n### 3.6 Configure linux systemd service\n\nSee [this](https://github.com/prashantgupta24/firewalld-rest/blob/master/firewalld-rest.service) for an example of a linux systemd service.\n\nThe `.service` file should be placed under `etc/systemd/system` directory.\n\n**Note**: This service assumes your binary is at `/root/rest/firewalld-rest`. You can change that in the file above.\n\n### 3.7 Start and enable systemd service.\n\n**Start**\n\n```\nsystemctl start firewalld-rest\n```\n\n**Logs**\n\nYou can see the logs for the service using:\n\n```\njournalctl -r\n```\n\n**Enable**\n\n```\nsystemctl enable firewalld-rest\n```\n\n### 3.8 IP JSON\n\nThis is how the IP JSON looks like, so that you know how you have to pass your IP and domain to the application:\n\n```\ntype IP struct {\n\tIP     string `json:\"ip\"`\n\tDomain string `json:\"domain\"`\n}\n```\n\n### 3.9 Interacting with the REST application\n\n#### 3.9.1 Index page\n\n```\nroute{\n    \"Index Page\",\n    \"GET\",\n    \"/\",\n}\n```\n\n##### Sample query\n\n```\ncurl --location --request GET '<SERVER_IP>:8080/m1' \\\n--header 'Authorization: Bearer <jwt>'\n```\n\n#### 3.9.2 Show all IPs\n\n```\nroute{\n    \"Show all IPs present\",\n    \"GET\",\n    \"/ip\",\n}\n```\n\n##### Sample query\n\n```\ncurl --location --request GET '<SERVER_IP>:8080/m1/ip' \\\n--header 'Authorization: Bearer <jwt>'\n```\n\n#### 3.9.3 Add new IP\n\n```\nroute{\n    \"Add New IP\",\n    \"POST\",\n    \"/ip\",\n}\n```\n\n##### Sample query\n\n```\ncurl --location --request POST '<SERVER_IP>:8080/m1/ip' \\\n--header 'Authorization: Bearer <jwt>' \\\n--header 'Content-Type: application/json' \\\n--data-raw '{\"ip\":\"10.xx.xx.xx\",\"domain\":\"example.com\"}'\n```\n\n#### 3.9.4 Show if IP is present\n\n```\nroute{\n    \"Show if particular IP is present\",\n    \"GET\",\n    \"/ip/{ip}\",\n}\n```\n\n##### Sample query\n\n```\ncurl --location --request GET '<SERVER_IP>:8080/m1/ip/10.xx.xx.xx' \\\n--header 'Authorization: Bearer <jwt>'\n```\n\n#### 3.9.5 Delete IP\n\n```\nroute{\n    \"Delete IP\",\n    \"DELETE\",\n    \"/ip/{ip}\",\n}\n```\n\n##### Sample query\n\n```\ncurl --location --request DELETE '<SERVER_IP>:8080/m1/ip/10.xx.xx.xx' \\\n--header 'Authorization: Bearer <jwt>'\n```\n\n## 4. Helpful tips/links\n\n- ### 4.1 Creating custom kubernetes endpoint\n\n  - https://theithollow.com/2019/02/04/kubernetes-endpoints/\n\n- ### 4.2 Firewalld rules\n\n  - https://www.digitalocean.com/community/tutorials/how-to-set-up-a-firewall-using-firewalld-on-centos-7\n\n  #### 4.2.1 Useful commands\n\n  ```\n  firewall-cmd --get-default-zone\n  firewall-cmd --get-active-zones\n\n  firewall-cmd --list-all-zones | less\n\n  firewall-cmd --zone=public --list-sources\n  firewall-cmd --zone=public --list-services\n  firewall-cmd --zone=public --list-all\n\n  firewall-cmd --zone=public --add-service=ssh --permanent\n\n  firewall-cmd --zone=internal --add-source=70.xx.xx.xxx/32 --permanent\n\n  firewall-cmd --reload\n  ```\n\n  #### 4.2.2 Rich rules\n\n  `firewall-cmd --permanent --zone=public --list-rich-rules`\n\n  `firewall-cmd --permanent --zone=public --add-rich-rule='rule family=\"ipv4\" source address=\"10.10.99.10/32\" port protocol=\"tcp\" port=\"22\" accept'`\n\n  `firewall-cmd --permanent --zone=public --add-rich-rule='rule family=\"ipv4\" source address=\"192.168.100.0/24\" invert=\"True\" drop'`\n\n  > Reject will reply back with an ICMP packet noting the rejection, while a drop will just silently drop the traffic and do nothing else, so a drop may be preferable in terms of security as a reject response confirms the existence of the system as it is rejecting the request.\n\n  #### 4.2.3 Misc tips\n\n  > --add-source=IP can be used to add an IP address or range of addresses to a zone. This will mean that if any source traffic enters the systems that matches this, the zone that we have set will be applied to that traffic. In this case we set the ‘testing’ zone to be associated with traffic from the 10.10.10.0/24 range.\n\n  ```\n  [root@centos7 ~]# firewall-cmd --permanent --zone=testing --add-source=10.10.10.0/24\n  success\n  ```\n\n- ### 4.3 Using JWT in Go\n\n  - https://www.thepolyglotdeveloper.com/2017/03/authenticate-a-golang-api-with-json-web-tokens/\n\n- ### 4.4 Using golang Exec\n\n  - https://stackoverflow.com/questions/39151420/golang-executing-command-with-spaces-in-one-of-the-parts\n\n- ### 4.5 Systemd services\n\n  - https://medium.com/@benmorel/creating-a-linux-service-with-systemd-611b5c8b91d6\n  - https://www.digitalocean.com/community/tutorials/understanding-systemd-units-and-unit-files\n  - [Logs using journalctl](https://www.linode.com/docs/quick-answers/linux/how-to-use-journalctl/)\n\n- ### 4.6 Using LDFlags in golang\n\n  - https://www.digitalocean.com/community/tutorials/using-ldflags-to-set-version-information-for-go-applications\n\n## 5. Commands for generating public/private key\n\n```\nopenssl genrsa -key private-key-sc.pem\nopenssl req -new -x509 -key private-key-sc.pem -out public.cert\n```\n\n[version-badge]: https://img.shields.io/github/v/release/prashantgupta24/firewalld-rest\n[releases]: https://github.com/prashantgupta24/firewalld-rest/releases\n\n## 6. Possible enhancements\n\n- Rate limiting the number of requests that can be made to the application\n"
  },
  {
    "path": "cmd/main.go",
    "content": "package main\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\n\t\"github.com/prashantgupta24/firewalld-rest/route\"\n)\n\nfunc main() {\n\tfmt.Println(\"starting application\")\n\n\trouter := route.NewRouter()\n\tlog.Fatal(http.ListenAndServe(\":8080\", router))\n}\n"
  },
  {
    "path": "db/db.go",
    "content": "package db\n\n//Instance DB interface for application\ntype Instance interface {\n\tType() string\n\tRegister(v interface{}) //needed for fileType. Can be left blank for other types of db\n\tSave(v interface{}) error\n\tLoad(v interface{}) error\n}\n"
  },
  {
    "path": "db/fileType.go",
    "content": "package db\n\nimport (\n\t\"bytes\"\n\t\"encoding/gob\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"os\"\n\t\"path/filepath\"\n\t\"sync\"\n)\n\nvar lock sync.Mutex\nvar once sync.Once\nvar pathFromEnv string //This will be set through the build command, see Makefile\n\nconst (\n\tfileName    = \"firewalld-rest.db\"\n\tdefaultPath = \"./\"\n)\n\n//singleton reference\nvar fileTypeInstance *fileType\n\n//fileType is the main struct for file database\ntype fileType struct {\n\tpath string\n}\n\n//GetFileTypeInstance returns the singleton instance of the filedb object\nfunc GetFileTypeInstance() Instance {\n\tonce.Do(func() {\n\t\tpath := defaultPath + fileName\n\t\tif pathFromEnv != \"\" {\n\t\t\tpathFromEnv = parsePath(pathFromEnv)\n\t\t\tpathFromEnv += fileName\n\t\t\tpath = pathFromEnv\n\t\t}\n\t\tfileTypeInstance = &fileType{path: path}\n\t})\n\treturn fileTypeInstance\n}\n\n//Type of the db\nfunc (fileType *fileType) Type() string {\n\treturn \"fileType\"\n}\n\n// Register interface with gob\nfunc (fileType *fileType) Register(v interface{}) {\n\tgob.Register(v)\n}\n\n// Save saves a representation of v to the file at path.\nfunc (fileType *fileType) Save(v interface{}) error {\n\tlock.Lock()\n\tdefer lock.Unlock()\n\tf, err := os.Create(fileType.path)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer f.Close()\n\tr, err := marshal(v)\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, err = io.Copy(f, r)\n\treturn err\n}\n\n// Load loads the file at path into v.\nfunc (fileType *fileType) Load(v interface{}) error {\n\tfullPath, err := filepath.Abs(fileType.path)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"could not locate absolute path : %v\", err)\n\t}\n\tif fileExists(fileType.path) {\n\t\tlock.Lock()\n\t\tdefer lock.Unlock()\n\t\tf, err := os.Open(fileType.path)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer f.Close()\n\t\treturn unmarshal(f, v)\n\t}\n\tlog.Printf(\"Db file not found, will be created here: %v\\n\", fullPath)\n\treturn nil\n}\n\n// marshal is a function that marshals the object into an\n// io.Reader.\nvar marshal = func(v interface{}) (io.Reader, error) {\n\tvar buf bytes.Buffer\n\te := gob.NewEncoder(&buf)\n\terr := e.Encode(v)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn bytes.NewReader(buf.Bytes()), nil\n}\n\n// unmarshal is a function that unmarshals the data from the\n// reader into the specified value.\nvar unmarshal = func(r io.Reader, v interface{}) error {\n\td := gob.NewDecoder(r)\n\terr := d.Decode(v)\n\tif err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n\n// fileExists checks if a file exists and is not a directory before we\n// try using it to prevent further errors.\nfunc fileExists(filename string) bool {\n\tinfo, err := os.Stat(filename)\n\tif os.IsNotExist(err) {\n\t\treturn false\n\t}\n\treturn !info.IsDir()\n}\n\nfunc parsePath(path string) string {\n\tlastChar := path[len(path)-1:]\n\n\tif lastChar != \"/\" {\n\t\tpath += \"/\"\n\t}\n\treturn path\n}\n"
  },
  {
    "path": "firewallcmd/util.go",
    "content": "package firewallcmd\n\nimport (\n\t\"fmt\"\n\t\"os/exec\"\n)\n\n//EnableRichRuleForIP enables rich rule for IP access + reloads\n//example:\n//firewall-cmd --permanent --zone=public --add-rich-rule='rule family=\"ipv4\" source address=\"10.10.99.10/32\" port protocol=\"tcp\" port=\"22\" accept'\nfunc EnableRichRuleForIP(ipAddr string) (string, error) {\n\tcmd1 := exec.Command(`firewall-cmd`, `--permanent`, \"--zone=public\", `--add-rich-rule=rule family=\"ipv4\" source address=\"`+ipAddr+`/32\" port protocol=\"tcp\" port=\"22\" accept`)\n\t//uncomment for debugging\n\t// for _, v := range cmd1.Args {\n\t// \tfmt.Println(v)\n\t// }\n\toutput1, err1 := cmd1.CombinedOutput()\n\tif err1 != nil {\n\t\treturn cmd1.String(), err1\n\t}\n\tfmt.Printf(\"rich rule added successfully for ip %v : %v\", ipAddr, string(output1))\n\n\tcmd2, output2, err2 := reload()\n\tif err2 != nil {\n\t\treturn cmd2.String(), err2\n\t}\n\tfmt.Printf(\"firewalld reloaded successfully : %v\", string(output2))\n\treturn \"\", nil\n}\n\n//DisableRichRuleForIP disables rich rule for IP access + reloads\nfunc DisableRichRuleForIP(ipAddr string) (string, error) {\n\tcmd1 := exec.Command(`firewall-cmd`, `--permanent`, \"--zone=public\", `--remove-rich-rule=rule family=\"ipv4\" source address=\"`+ipAddr+`/32\" port protocol=\"tcp\" port=\"22\" accept`)\n\toutput1, err1 := cmd1.CombinedOutput()\n\tif err1 != nil {\n\t\treturn cmd1.String(), err1\n\t}\n\tfmt.Printf(\"rich rule deleted successfully for ip %v : %v\", ipAddr, string(output1))\n\n\tcmd2, output2, err2 := reload()\n\tif err2 != nil {\n\t\treturn cmd2.String(), err2\n\t}\n\tfmt.Printf(\"firewalld reloaded successfully : %v\", string(output2))\n\treturn \"\", nil\n}\n\n//reload reloads firewall for setting to take effect\nfunc reload() (*exec.Cmd, []byte, error) {\n\tcmd := exec.Command(\"firewall-cmd\", \"--reload\")\n\toutput, err := cmd.CombinedOutput()\n\treturn cmd, output, err\n}\n"
  },
  {
    "path": "firewalld-rest.service",
    "content": "[Unit]\nDescription=Firewalld rest service\nAfter=network.target\n\n[Service]\nType=simple\nRestart=always\nRestartSec=1\nUser=root\nExecStart=/root/rest/firewalld-rest\n\n[Install]\nWantedBy=multi-user.target"
  },
  {
    "path": "go.mod",
    "content": "module github.com/prashantgupta24/firewalld-rest\n\ngo 1.14\n\nrequire (\n\tgithub.com/dgrijalva/jwt-go v3.2.0+incompatible\n\tgithub.com/gorilla/mux v1.7.4\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=\ngithub.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=\ngithub.com/gorilla/mux v1.7.4 h1:VuZ8uybHlWmqV03+zRzdwKL4tUnIp1MAQtp1mIFE1bc=\ngithub.com/gorilla/mux v1.7.4/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=\n"
  },
  {
    "path": "ip/handler.go",
    "content": "package ip\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"sync\"\n\n\t\"github.com/prashantgupta24/firewalld-rest/db\"\n)\n\nvar once sync.Once\n\n//singleton reference\nvar handlerInstance *handlerStruct\n\n//handlerStruct for managing IP related tasks\ntype handlerStruct struct {\n\tdb db.Instance\n}\n\n//GetHandler gets singleton handler for IP management\nfunc GetHandler() Handler {\n\tonce.Do(func() {\n\t\tdbInstance := db.GetFileTypeInstance()\n\t\thandlerInstance = &handlerStruct{\n\t\t\tdb: dbInstance,\n\t\t}\n\t\tipStore, err := handlerInstance.loadIPStore()\n\t\tif err != nil {\n\t\t\tlog.Fatal(err)\n\t\t}\n\t\tif len(ipStore) == 0 {\n\t\t\t//in case you want to store some IPs before hand\n\t\t\t// ipStore[\"1.2.3.4\"] = &Instance{\n\t\t\t// \tIP:     \"1.2.3.4\",\n\t\t\t// \tDomain: \"first.com\",\n\t\t\t// }\n\t\t\t// ipStore[\"5.6.7.8\"] = &Instance{\n\t\t\t// \tIP:     \"5.6.7.8\",\n\t\t\t// \tDomain: \"second\",\n\t\t\t// }\n\t\t\thandlerInstance.db.Register(ipStore)\n\t\t\tif err := handlerInstance.saveIPStore(ipStore); err != nil {\n\t\t\t\tlog.Fatal(err)\n\t\t\t}\n\t\t}\n\t})\n\treturn handlerInstance\n}\n\n//GetIP from the db\nfunc (handler *handlerStruct) GetIP(ipAddr string) (*Instance, error) {\n\tipStore, err := handler.loadIPStore()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tip, ok := ipStore[ipAddr]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"record not found\")\n\t}\n\treturn ip, nil\n}\n\n//GetAllIPs from the db\nfunc (handler *handlerStruct) GetAllIPs() ([]*Instance, error) {\n\tips := []*Instance{}\n\tipStore, err := handler.loadIPStore()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfor _, ip := range ipStore {\n\t\tips = append(ips, ip)\n\t}\n\treturn ips, nil\n}\n\n//CheckIPExists checks if IP is in db\nfunc (handler *handlerStruct) CheckIPExists(ipAddr string) (bool, error) {\n\tipStore, err := handler.loadIPStore()\n\tif err != nil {\n\t\treturn false, err\n\t}\n\t_, ok := ipStore[ipAddr]\n\tif ok {\n\t\treturn true, nil\n\t}\n\treturn false, nil\n}\n\n//AddIP to the db\nfunc (handler *handlerStruct) AddIP(ip *Instance) error {\n\tipStore, err := handler.loadIPStore()\n\tif err != nil {\n\t\treturn err\n\t}\n\t_, ok := ipStore[ip.IP]\n\tif ok {\n\t\treturn fmt.Errorf(\"ip already exists\")\n\t}\n\tipStore[ip.IP] = ip\n\tif err := handler.saveIPStore(ipStore); err != nil {\n\t\treturn fmt.Errorf(\"error while saving to file : %v\", err)\n\t}\n\treturn nil\n}\n\n//DeleteIP from the db\nfunc (handler *handlerStruct) DeleteIP(ipAddr string) (*Instance, error) {\n\tipStore, err := handler.loadIPStore()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tip, ok := ipStore[ipAddr]\n\tif !ok {\n\t\treturn nil, fmt.Errorf(\"record not found\")\n\t}\n\tdelete(ipStore, ipAddr)\n\tif err := handler.saveIPStore(ipStore); err != nil {\n\t\treturn nil, fmt.Errorf(\"error while saving to file : %v\", err)\n\t}\n\treturn ip, nil\n}\n\nfunc (handler *handlerStruct) loadIPStore() (map[string]*Instance, error) {\n\tvar ipStore = make(map[string]*Instance)\n\tif err := handler.db.Load(&ipStore); err != nil {\n\t\treturn nil, fmt.Errorf(\"error while loading from file : %v\", err)\n\t}\n\treturn ipStore, nil\n}\n\nfunc (handler *handlerStruct) saveIPStore(ipStore map[string]*Instance) error {\n\tif err := handler.db.Save(ipStore); err != nil {\n\t\treturn fmt.Errorf(\"error while saving to file : %v\", err)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "ip/handler_test.go",
    "content": "package ip\n\nimport (\n\t\"log\"\n\t\"os\"\n\t\"os/exec\"\n\t\"strings\"\n\t\"testing\"\n)\n\nvar handler Handler\nvar ipAddr string\nvar ipInstance *Instance\n\nfunc setup() {\n\thandler = GetHandler()\n\tipAddr = \"10.20.30.40\"\n\tipInstance = &Instance{\n\t\tIP:     ipAddr,\n\t\tDomain: \"test\",\n\t}\n}\n\nfunc shutdown() {\n\tos.Remove(\"firewalld-rest.db\")\n}\n\nfunc TestMain(m *testing.M) {\n\tsetup()\n\tcode := m.Run()\n\tshutdown()\n\tos.Exit(code)\n}\n\nfunc TestGetHandler(t *testing.T) {\n\tif handler == nil {\n\t\tt.Errorf(\"handler should not be nil\")\n\t}\n}\n\nfunc TestGetAllIPsFileError(t *testing.T) {\n\tchangeFilePermission(\"100\")\n\t_, err := handler.GetAllIPs()\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\tchangeFilePermission(\"644\")\n}\n\nfunc TestGetAllIPs(t *testing.T) {\n\tips, err := handler.GetAllIPs()\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n\tif len(ips) != 0 {\n\t\tt.Errorf(\"should have been an empty list, instead got : %v\", len(ips))\n\t}\n}\n\nfunc TestAddIPFileError(t *testing.T) {\n\tchangeFilePermission(\"100\")\n\terr := handler.AddIP(ipInstance)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\n\tchangeFilePermission(\"500\")\n\terr = handler.AddIP(ipInstance)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\tchangeFilePermission(\"644\")\n}\nfunc TestAddIP(t *testing.T) {\n\terr := handler.AddIP(ipInstance)\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n}\n\nfunc TestAddIPDup(t *testing.T) {\n\terr := handler.AddIP(ipInstance)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored for duplicate IP\")\n\t}\n}\n\nfunc TestGetAllIPsAfterAdd(t *testing.T) {\n\tips, err := handler.GetAllIPs()\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n\tif len(ips) == 0 {\n\t\tt.Errorf(\"should have included %v , instead got : %v\", ipAddr, ips)\n\t}\n}\n\nfunc TestCheckIPExistsFileError(t *testing.T) {\n\tchangeFilePermission(\"100\")\n\t_, err := handler.CheckIPExists(ipAddr)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\tchangeFilePermission(\"644\")\n}\n\nfunc TestCheckIPExists(t *testing.T) {\n\tipExists, err := handler.CheckIPExists(ipAddr)\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n\tif !ipExists {\n\t\tt.Errorf(\"ip %v should exist\", ipAddr)\n\t}\n}\n\nfunc TestGetIPFileError(t *testing.T) {\n\tchangeFilePermission(\"100\")\n\t_, err := handler.GetIP(ipAddr)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\tchangeFilePermission(\"644\")\n}\n\nfunc TestGetInvalidIP(t *testing.T) {\n\t_, err := handler.GetIP(\"invalid_ip\")\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && err.Error() != \"record not found\" {\n\t\tt.Errorf(\"record should not have been found, instead got : %v\", err)\n\t}\n}\n\nfunc TestGetIP(t *testing.T) {\n\tipRecd, err := handler.GetIP(ipAddr)\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n\tif ipRecd.IP != ipInstance.IP {\n\t\tt.Errorf(\"ip should be same, got %v want %v\", ipRecd.IP, ipInstance.IP)\n\t}\n}\n\nfunc TestDeleteIPFileError(t *testing.T) {\n\tchangeFilePermission(\"100\")\n\t_, err := handler.DeleteIP(ipAddr)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\n\tchangeFilePermission(\"500\")\n\t_, err = handler.DeleteIP(ipAddr)\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && strings.Index(err.Error(), \"permission denied\") == -1 {\n\t\tt.Errorf(\"should have received permission error, instead got : %v\", err)\n\t}\n\tchangeFilePermission(\"644\")\n}\n\nfunc TestDeleteInvalidIP(t *testing.T) {\n\t_, err := handler.DeleteIP(\"invalid_ip\")\n\tif err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\tif err != nil && err.Error() != \"record not found\" {\n\t\tt.Errorf(\"record should not have been found, instead got : %v\", err)\n\t}\n}\n\nfunc TestDeleteIP(t *testing.T) {\n\tipDeleted, err := handler.DeleteIP(ipAddr)\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n\tif ipAddr != ipDeleted.IP {\n\t\tt.Errorf(\"ip %v should be the same\", ipAddr)\n\t}\n\tipExists, err := handler.CheckIPExists(ipAddr)\n\tif err != nil {\n\t\tt.Errorf(\"should not have errored, err : %v\", err)\n\t}\n\tif ipExists {\n\t\tt.Errorf(\"ip %v should be deleted\", ipAddr)\n\t}\n}\n\nfunc changeFilePermission(permission string) {\n\tcmd := exec.Command(\"chmod\", permission, \"firewalld-rest.db\")\n\terr := cmd.Run()\n\tif err != nil {\n\t\tlog.Fatalf(\"could not change permission of file, err : %v\", err)\n\t}\n\t// cmd1 := exec.Command(\"ls\", \"-la\")\n\t// o1, _ := cmd1.CombinedOutput()\n\t// fmt.Println(\"cmd1 : \", string(o1))\n}\n"
  },
  {
    "path": "ip/ip.go",
    "content": "package ip\n\n// Instance holds json for ip and domain\ntype Instance struct {\n\tIP     string `json:\"ip\"`\n\tDomain string `json:\"domain\"`\n}\n\n//Handler interface for handling IP related tasks\ntype Handler interface {\n\tGetIP(string) (*Instance, error)\n\tGetAllIPs() ([]*Instance, error)\n\tCheckIPExists(ipAddr string) (bool, error)\n\tAddIP(ip *Instance) error\n\tDeleteIP(ipAddr string) (*Instance, error)\n}\n"
  },
  {
    "path": "k8s/ingress.yaml",
    "content": "apiVersion: extensions/v1beta1\nkind: Ingress\nmetadata:\n name: firewalld-ingress\nspec:\n rules:\n - host: <your-host-name>\n   http:\n    #different paths for different hosts under the same k8s config\n     paths:\n     - path: /m1\n       backend:\n         serviceName: external-rest\n         servicePort: 80\n     - path: /m2\n       backend:\n         serviceName: external-rest-2\n         servicePort: 80\n     - path: /m3\n       backend:\n         serviceName: external-rest-3\n         servicePort: 80"
  },
  {
    "path": "k8s/svc-nodeport.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: external-rest\nspec:\n  externalIPs:\n  - 169.xx.xx.xxx # public IP of the server\n  type: NodePort\n  ports:\n  - name: firewalld\n    protocol: TCP\n    port: 8080\n    targetPort: 8080\n---\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: external-rest\nsubsets:\n  - addresses:\n      - ip: 10.xx.xx.xx #private IP of server\n    ports:\n      - port: 8080\n        name: firewalld"
  },
  {
    "path": "k8s/svc.yaml",
    "content": "apiVersion: v1\nkind: Service\nmetadata:\n  name: external-rest #external-rest-2 for 2nd machine and so on\nspec:\n  ports:\n  - name: firewalld\n    protocol: TCP\n    port: 80\n    targetPort: 8080\n---\napiVersion: v1\nkind: Endpoints\nmetadata:\n  name: external-rest #external-rest-2 for 2nd machine and so on\nsubsets:\n  - addresses:\n      - ip: 10.xx.xx.xx #private IP of node\n    ports:\n      - port: 8080\n        name: firewalld"
  },
  {
    "path": "route/handler.go",
    "content": "package route\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"io/ioutil\"\n\t\"log\"\n\t\"net/http\"\n\t\"os\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/prashantgupta24/firewalld-rest/firewallcmd\"\n\t\"github.com/prashantgupta24/firewalld-rest/ip\"\n)\n\n//Index page\n// GET /\nfunc Index(w http.ResponseWriter, r *http.Request) {\n\tfmt.Fprint(w, \"Welcome!\\n\")\n}\n\n// IPAdd for the Create action\n// POST /ip\nfunc IPAdd(w http.ResponseWriter, r *http.Request) {\n\tipInstance := &ip.Instance{}\n\tif err := populateModelFromHandler(w, r, ipInstance); err != nil {\n\t\twriteErrorResponse(w, http.StatusUnprocessableEntity, \"Unprocessible Entity\")\n\t\treturn\n\t}\n\tipExists, err := ip.GetHandler().CheckIPExists(ipInstance.IP)\n\tif err != nil {\n\t\twriteErrorResponse(w, http.StatusInternalServerError, err.Error())\n\t\treturn\n\t}\n\tif ipExists {\n\t\twriteErrorResponse(w, http.StatusBadRequest, \"ip already exists\")\n\t\treturn\n\t}\n\n\tenv := os.Getenv(\"env\")\n\tif env != \"local\" {\n\t\tcommand, err := firewallcmd.EnableRichRuleForIP(ipInstance.IP)\n\t\tif err != nil {\n\t\t\twriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf(\"cannot exec command %v, err : %v\", command, err.Error()))\n\t\t\treturn\n\t\t}\n\t}\n\n\terr = ip.GetHandler().AddIP(ipInstance)\n\tif err != nil {\n\t\twriteErrorResponse(w, http.StatusInternalServerError, err.Error())\n\t\treturn\n\t}\n\n\twriteOKResponse(w, ipInstance)\n}\n\n// IPShow for the ip Show action\n// GET /ip/{ip}\nfunc IPShow(w http.ResponseWriter, r *http.Request) {\n\tvars := mux.Vars(r)\n\tipAddr := vars[\"ip\"]\n\tip, err := ip.GetHandler().GetIP(ipAddr)\n\tif err != nil {\n\t\t// No IP found\n\t\twriteErrorResponse(w, http.StatusNotFound, err.Error())\n\t\treturn\n\t}\n\twriteOKResponse(w, ip)\n}\n\n// ShowAllIPs shows all IPs\n// GET /ip\nfunc ShowAllIPs(w http.ResponseWriter, r *http.Request) {\n\tips, err := ip.GetHandler().GetAllIPs()\n\tif err != nil {\n\t\twriteErrorResponse(w, http.StatusInternalServerError, err.Error())\n\t\treturn\n\t}\n\twriteOKResponse(w, ips)\n}\n\n// IPDelete for the ip Delete action\n// DELETE /ip/{ip}\nfunc IPDelete(w http.ResponseWriter, r *http.Request) {\n\tvars := mux.Vars(r)\n\tipAddr := vars[\"ip\"]\n\tlog.Printf(\"IP to delete %s\\n\", ipAddr)\n\n\tipExists, err := ip.GetHandler().CheckIPExists(ipAddr)\n\tif err != nil {\n\t\twriteErrorResponse(w, http.StatusInternalServerError, err.Error())\n\t\treturn\n\t}\n\tif !ipExists {\n\t\twriteErrorResponse(w, http.StatusNotFound, \"ip does not exist\")\n\t\treturn\n\t}\n\n\tenv := os.Getenv(\"env\")\n\tif env != \"local\" {\n\t\tcommand, err := firewallcmd.DisableRichRuleForIP(ipAddr)\n\t\tif err != nil {\n\t\t\twriteErrorResponse(w, http.StatusInternalServerError, fmt.Sprintf(\"cannot exec command %v, err : %v\", command, err.Error()))\n\t\t\treturn\n\t\t}\n\t}\n\n\tip, err := ip.GetHandler().DeleteIP(ipAddr)\n\tif err != nil {\n\t\t// IP could not be deleted\n\t\twriteErrorResponse(w, http.StatusNotFound, err.Error())\n\t\treturn\n\t}\n\twriteOKResponse(w, ip)\n}\n\n// Writes the response as a standard JSON response with StatusOK\nfunc writeOKResponse(w http.ResponseWriter, m interface{}) {\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\n\tw.WriteHeader(http.StatusOK)\n\tif err := json.NewEncoder(w).Encode(&JSONResponse{Data: m}); err != nil {\n\t\twriteErrorResponse(w, http.StatusInternalServerError, \"Internal Server Error\")\n\t}\n}\n\n// Writes the error response as a Standard API JSON response with a response code\nfunc writeErrorResponse(w http.ResponseWriter, errorCode int, errorMsg string) {\n\tw.Header().Set(\"Content-Type\", \"application/json; charset=UTF-8\")\n\tw.WriteHeader(errorCode)\n\tjson.\n\t\tNewEncoder(w).\n\t\tEncode(&JSONErrorResponse{Error: &APIError{Status: errorCode, Title: errorMsg}})\n}\n\n//Populates a ip from the params in the Handler\nfunc populateModelFromHandler(w http.ResponseWriter, r *http.Request, ip interface{}) error {\n\tbody, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576))\n\tif err != nil {\n\t\treturn err\n\t}\n\tif err := r.Body.Close(); err != nil {\n\t\treturn err\n\t}\n\tif err := json.Unmarshal(body, ip); err != nil {\n\t\treturn err\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "route/handler_test.go",
    "content": "package route\n\nimport (\n\t\"net/http\"\n\t\"net/http/httptest\"\n\t\"os\"\n\t\"strings\"\n\t\"testing\"\n\n\t\"github.com/gorilla/mux\"\n\t\"github.com/prashantgupta24/firewalld-rest/ip\"\n)\n\nvar data1 string\nvar data2 string\nvar dataInvalid string\nvar ipAddr1 string\nvar ipAddr2 string\nvar ipAddr3 string\n\nfunc setup() {\n\tipAddr1 = \"10.20.30.40\"\n\tipAddr2 = \"20.40.60.80\"\n\tipAddr3 = \"10.50.100.150\"\n\tdata1 = `{\"ip\":\"` + ipAddr1 + `\",\"domain\":\"test.com\"}`\n\tdata2 = `{\"ip\":\"` + ipAddr2 + `\",\"domain\":\"test.com\"}`\n\tdataInvalid = `{\"ip\":\"` + ipAddr2 //missing domain\n}\n\nfunc shutdown() {\n\tos.Remove(\"firewalld-rest.db\")\n}\n\nfunc TestMain(m *testing.M) {\n\tsetup()\n\tcode := m.Run()\n\tshutdown()\n\tos.Exit(code)\n}\n\nfunc TestIndex(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, Index)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `Welcome!`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestShowAllIPs(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/ip\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, ShowAllIPs)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"meta\":null,\"data\":[]}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestAddBadIP(t *testing.T) {\n\treq, err := http.NewRequest(\"POST\", \"/ip\", strings.NewReader(dataInvalid))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, IPAdd)\n\n\tif status := rr.Code; status != http.StatusUnprocessableEntity {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"error\":{\"status\":422,\"title\":\"Unprocessible Entity\"}}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestAddIPNonLocal(t *testing.T) {\n\toldEnv := os.Getenv(\"env\")\n\tos.Setenv(\"env\", \"staging\")\n\treq, err := http.NewRequest(\"POST\", \"/ip\", strings.NewReader(data1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, IPAdd)\n\n\tif status := rr.Code; status != http.StatusInternalServerError {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `cannot exec command `\n\tif strings.Index(rr.Body.String(), expected) == -1 {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot %v should have included : %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n\tos.Setenv(\"env\", oldEnv)\n}\n\nfunc TestAddIP(t *testing.T) {\n\treq, err := http.NewRequest(\"POST\", \"/ip\", strings.NewReader(data1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, IPAdd)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"meta\":null,\"data\":` + data1 + `}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestAddIPDup(t *testing.T) {\n\treq, err := http.NewRequest(\"POST\", \"/ip\", strings.NewReader(data1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, IPAdd)\n\n\tif status := rr.Code; status != http.StatusBadRequest {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"error\":{\"status\":400,\"title\":\"ip already exists\"}}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestShowIP(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/ip/\"+ipAddr1, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := httptest.NewRecorder()\n\trouter := mux.NewRouter()\n\trouter.HandleFunc(\"/ip/{ip}\", IPShow)\n\trouter.ServeHTTP(rr, req)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"meta\":null,\"data\":` + data1 + `}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestShowIPNotFound(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/ip/\"+ipAddr3, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := httptest.NewRecorder()\n\trouter := mux.NewRouter()\n\trouter.HandleFunc(\"/ip/{ip}\", IPShow)\n\trouter.ServeHTTP(rr, req)\n\n\tif status := rr.Code; status != http.StatusNotFound {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"error\":{\"status\":404,\"title\":\"record not found\"}}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestAddIP2(t *testing.T) {\n\treq, err := http.NewRequest(\"POST\", \"/ip\", strings.NewReader(data2))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, IPAdd)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"meta\":null,\"data\":` + data2 + `}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestShowAllIPsAfterAdding(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/ip\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, ShowAllIPs)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\t//make sure both IPs exist\n\tif strings.Index(rr.Body.String(), ipAddr1) == -1 || strings.Index(rr.Body.String(), ipAddr2) == -1 {\n\t\tt.Errorf(\"handler returned without required body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), ipAddr1)\n\t}\n}\n\nfunc TestDeleteIPNonLocal(t *testing.T) {\n\toldEnv := os.Getenv(\"env\")\n\tos.Setenv(\"env\", \"staging\")\n\treq, err := http.NewRequest(\"DELETE\", \"/ip/\"+ipAddr1, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := httptest.NewRecorder()\n\trouter := mux.NewRouter()\n\trouter.HandleFunc(\"/ip/{ip}\", IPDelete)\n\trouter.ServeHTTP(rr, req)\n\n\tif status := rr.Code; status != http.StatusInternalServerError {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `cannot exec command `\n\tif strings.Index(rr.Body.String(), expected) == -1 {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot %v should have included : %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n\tos.Setenv(\"env\", oldEnv)\n}\n\nfunc TestDeleteIP(t *testing.T) {\n\treq, err := http.NewRequest(\"DELETE\", \"/ip/\"+ipAddr1, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := httptest.NewRecorder()\n\trouter := mux.NewRouter()\n\trouter.HandleFunc(\"/ip/{ip}\", IPDelete)\n\trouter.ServeHTTP(rr, req)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"meta\":null,\"data\":` + data1 + `}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestDeleteIPNotFound(t *testing.T) {\n\treq, err := http.NewRequest(\"DELETE\", \"/ip/\"+ipAddr1, nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := httptest.NewRecorder()\n\trouter := mux.NewRouter()\n\trouter.HandleFunc(\"/ip/{ip}\", IPDelete)\n\trouter.ServeHTTP(rr, req)\n\n\tif status := rr.Code; status != http.StatusNotFound {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusNotFound)\n\t}\n\n\t// Check the response body is what we expect.\n\texpected := `{\"error\":{\"status\":404,\"title\":\"ip does not exist\"}}`\n\tif strings.TrimSpace(rr.Body.String()) != expected {\n\t\tt.Errorf(\"handler returned unexpected body: \\ngot  %v want %v\",\n\t\t\trr.Body.String(), expected)\n\t}\n}\n\nfunc TestShowAllIPsAfter(t *testing.T) {\n\treq, err := http.NewRequest(\"GET\", \"/ip\", nil)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\trr := newRequestRecorder(req, ShowAllIPs)\n\n\tif status := rr.Code; status != http.StatusOK {\n\t\tt.Errorf(\"handler returned wrong status code: got %v want %v\",\n\t\t\tstatus, http.StatusOK)\n\t}\n\t//make sure ip doesn't exist\n\tif strings.Index(rr.Body.String(), ipAddr1) != -1 {\n\t\tt.Errorf(\"handler contained deleted entry: \\ngot  %vdeleted %v\",\n\t\t\trr.Body.String(), ipAddr1)\n\t}\n}\n\nfunc TestPopulateModelFromHandler(t *testing.T) {\n\n\treq, err := http.NewRequest(\"POST\", \"/ip\", strings.NewReader(\"garbage\"))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tipInstance := &ip.Instance{}\n\tif err := populateModelFromHandler(nil, req, ipInstance); err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\n\treq, err = http.NewRequest(\"POST\", \"/ip\", strings.NewReader(dataInvalid))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tipInstance = &ip.Instance{}\n\tif err := populateModelFromHandler(nil, req, ipInstance); err == nil {\n\t\tt.Errorf(\"should have errored\")\n\t}\n\n\treq, err = http.NewRequest(\"POST\", \"/ip\", strings.NewReader(data1))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tipInstance = &ip.Instance{}\n\tif err := populateModelFromHandler(nil, req, ipInstance); err != nil {\n\t\tt.Errorf(\"should not have errored : %v\", err)\n\t}\n}\n\n// Mocks a handler and returns a httptest.ResponseRecorder\nfunc newRequestRecorder(req *http.Request, fnHandler func(w http.ResponseWriter, r *http.Request)) *httptest.ResponseRecorder {\n\trr := httptest.NewRecorder()\n\thandler := http.HandlerFunc(fnHandler)\n\thandler.ServeHTTP(rr, req)\n\treturn rr\n}\n"
  },
  {
    "path": "route/middleware.go",
    "content": "package route\n\nimport (\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"log\"\n\t\"net/http\"\n\t\"strings\"\n\n\t\"github.com/dgrijalva/jwt-go\"\n)\n\ntype exception struct {\n\tMessage string `json:\"message\"`\n}\n\n//validateMiddleware validates the JWT\nfunc validateMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tauthorizationHeader := req.Header.Get(\"authorization\")\n\t\tif authorizationHeader != \"\" {\n\t\t\t//fmt.Println(\"authorizationHeader : \", authorizationHeader)\n\t\t\tbearerToken := strings.Split(authorizationHeader, \" \")\n\t\t\tif len(bearerToken) == 2 {\n\t\t\t\tkey, err := jwt.ParseRSAPublicKeyFromPEM([]byte(publicCertContent))\n\t\t\t\tif err != nil {\n\t\t\t\t\tjson.NewEncoder(w).Encode(exception{Message: err.Error()})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\ttoken, error := jwt.Parse(bearerToken[1], func(token *jwt.Token) (interface{}, error) {\n\t\t\t\t\tif _, ok := token.Method.(*jwt.SigningMethodRSA); !ok {\n\t\t\t\t\t\treturn nil, fmt.Errorf(\"There was an error\")\n\t\t\t\t\t}\n\t\t\t\t\treturn key, nil\n\t\t\t\t})\n\t\t\t\tif error != nil {\n\t\t\t\t\tjson.NewEncoder(w).Encode(exception{Message: error.Error()})\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t\tif token.Valid {\n\t\t\t\t\tnext.ServeHTTP(w, req)\n\t\t\t\t} else {\n\t\t\t\t\tjson.NewEncoder(w).Encode(exception{Message: \"Invalid authorization token\"})\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tjson.NewEncoder(w).Encode(exception{Message: \"An authorization header is required\"})\n\t\t}\n\t})\n}\n\nfunc loggingMiddleware(next http.Handler) http.Handler {\n\treturn http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n\t\t// Do stuff here\n\t\tlog.Println(r.RequestURI)\n\t\t// Call the next handler, which can be another middleware in the chain, or the final handler.\n\t\tnext.ServeHTTP(w, r)\n\t})\n}\n"
  },
  {
    "path": "route/publicCert.go",
    "content": "package route\n\n//publicCertContent required for authentication\nconst publicCertContent = `\n-----BEGIN CERTIFICATE-----\nMIIDgjCCAmoCCQDybHZ/ZguMATANBgkqhkiG9w0BAQsFADCBgjELMAkGA1UEBhMC\nVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExETAPBgNVBAcMCFNhbiBKb3NlMQwwCgYD\nVQQKDANJQk0xFDASBgNVBAsMC0RhdGEgYW5kIEFJMScwJQYJKoZIhvcNAQkBFhhw\ncmFzaGFudGd1cHRhQHVzLmlibS5jb20wHhcNMjAwNjExMjA1MDU1WhcNMjAwNzEx\nMjA1MDU1WjCBgjELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3JuaWExETAP\nBgNVBAcMCFNhbiBKb3NlMQwwCgYDVQQKDANJQk0xFDASBgNVBAsMC0RhdGEgYW5k\nIEFJMScwJQYJKoZIhvcNAQkBFhhwcmFzaGFudGd1cHRhQHVzLmlibS5jb20wggEi\nMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDnQKV05br1v8+lyt+js9Lhhpej\nQEjLQvWKkeWIC+VwXekXIrraM8Z1MImI9hAGDgyK7b18uk3paZ7KxTxtaWxVodUU\n75qDEsPRy8byQjAdDKnccMvDI1LMQ1HHw6Hfv8GEOcBebKwdsbtpBYtqKsaZn95N\nIw8k2alDAUVe8y840lTzOhowsaN9I4bQV2nNosfWWYGOCacV8vIquJ0s71BdFk7A\ngXAO4vONEwrpJICiBRQRWTcKFNeCBoNn5zNFs0LsncrhKajENeXT+NfLJUVLEwTc\n0X0yqz587rlQrKY7/xDQukVhGq1HCRMeWbeK87/jcTf8QyQu4nbqrgAJPzQBAgMB\nAAEwDQYJKoZIhvcNAQELBQADggEBALhCTiALoDxLpa3KK3utgBG7VUcz3pbhnCdp\nQj1y/U3lQ5Am+Lztc9V0CpbVRlX/B2Kmj3Eln+JOPnucHFOv4VbY6cE7oOIzgcAY\nTp6/rn1KFXlb72cRahpFgnP7m2WWKXDibHNx7H4nGpmDNjMgmCgLP4KisauMrork\nCTEqVJk5abC/JCO7HBUBZkCsY0GgxmZDivWcpzxHjl7/U5dSlUEIlbcrmBaVN3fy\naATm38m1e1h/ZE2gQRmfumA9DMEs3HJ4fLbEnpxJ+dJJM6uu7/jJaz1aPvNaNYby\nsAFLryXHIMWAKDO6o02IhcFpbw0T4GxpTYqsEcj7BrdbFdsUeN0=\n-----END CERTIFICATE-----\n`\n"
  },
  {
    "path": "route/response.go",
    "content": "package route\n\n// JSONResponse defines meta and interface struct\ntype JSONResponse struct {\n\t// Reserved field to add some meta information to the API response\n\tMeta interface{} `json:\"meta\"`\n\tData interface{} `json:\"data\"`\n}\n\n// JSONErrorResponse defines error struct\ntype JSONErrorResponse struct {\n\tError *APIError `json:\"error\"`\n}\n\n// APIError defines api error struct\ntype APIError struct {\n\tStatus int    `json:\"status\"`\n\tTitle  string `json:\"title\"`\n}\n"
  },
  {
    "path": "route/route.go",
    "content": "package route\n\nimport (\n\t\"net/http\"\n\n\t\"github.com/gorilla/mux\"\n)\n\ntype route struct {\n\tName        string\n\tMethod      string\n\tPattern     string\n\tHandlerFunc http.HandlerFunc\n}\n\ntype routes []route\n\n//NewRouter creates a new mux router for application\nfunc NewRouter() *mux.Router {\n\n\trouter := mux.NewRouter()\n\tsubrouter := router.PathPrefix(\"/m{[0-9]+}\").Subrouter().StrictSlash(true)\n\n\tsubrouter.Use(loggingMiddleware, validateMiddleware)\n\tfor _, route := range routesForApp {\n\t\tsubrouter.\n\t\t\tMethods(route.Method).\n\t\t\tPath(route.Pattern).\n\t\t\tName(route.Name).\n\t\t\tHandler(route.HandlerFunc)\n\t}\n\n\treturn subrouter\n}\n\nvar routesForApp = routes{\n\troute{\n\t\t\"Index Page\",\n\t\t\"GET\",\n\t\t\"/\",\n\t\tIndex,\n\t},\n\troute{\n\t\t\"Add New IP\",\n\t\t\"POST\",\n\t\t\"/ip\",\n\t\tIPAdd,\n\t},\n\troute{\n\t\t\"Show all IPs present\",\n\t\t\"GET\",\n\t\t\"/ip\",\n\t\tShowAllIPs,\n\t},\n\troute{\n\t\t\"Show if particular IP is present\",\n\t\t\"GET\",\n\t\t\"/ip/{ip}\",\n\t\tIPShow,\n\t},\n\troute{\n\t\t\"Delete IP\",\n\t\t\"DELETE\",\n\t\t\"/ip/{ip}\",\n\t\tIPDelete,\n\t},\n}\n"
  }
]