En este tutorial veremos un ejemplo de API REST bajo Go que inserta los datos recibidos en una base de datos InfluxDB, además esta soportará autenticación y SSL. Pienso que es un buen ejemplo ya que el código no es excesivamente complejo y auto explicativo.
// kr0m InfluxDB API: v0.2b
package main
import (
"fmt"
"time"
"context"
"regexp"
"strings"
//"log"
"net/http"
"github.com/gin-gonic/gin"
"github.com/influxdata/influxdb-client-go/v2"
"github.com/influxdata/influxdb-client-go/v2/api/write"
//"github.com/davecgh/go-spew/spew"
)
// Insertion register structure
type insertionRegister struct {
Measurement string `json:"Measurement" binding:"required"`
Phone string `json:"Phone" binding:"required"`
Project string `json:"Project" binding:"required"`
Group string `json:"Group" binding:"required"`
// No boolean type in order to save it as InfluxDB label
Spam string `json:"Spam" binding:"required"`
Google string `json:"Google" binding:"required"`
ShouldAnswer string `json:"ShouldAnswer" binding:"required"`
}
func main() {
fmt.Println("----------------------------")
fmt.Println("| InfluxDB API: v0.2b-kr0m |")
fmt.Println("----------------------------")
router := setUpRouter()
//router.Run(":9000")
router.RunTLS(":9000", "/etc/letsencrypt/live/goapitest.aflaexploit.com/fullchain.pem", "/etc/letsencrypt/live/goapitest.aflaexploit.com/privkey.pem")
}
// Separate setUpRouter function in order to call it also from unit tests
func setUpRouter() *gin.Engine {
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
router.POST("/insertdata", gin.BasicAuth(gin.Accounts{"kr0m": "XXXXXX"}), insertData)
return router
}
// curl -v -X POST https://goapitest.aflaexploit.com:9000/insertdata -H 'content-type: application/json' -d '{ "Measurement":"kr0m", "Phone":"123445566", "Project":"kr0mProject", "Group":"kr0mGroup", "Spam":"false", "Google":"false", "ShouldAnswer":"false" }' -u "kr0m:XXXXXX"
func insertData(c *gin.Context) {
var newInsertionRegister insertionRegister
if err := c.BindJSON(&newInsertionRegister); err != nil {
fmt.Println(err)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
} else {
// Sanity check of received fields
// Measurement string length:
length := len(newInsertionRegister.Measurement)
if length<1 || length>=50 {
fmt.Println("Invalid Measurement string length: ", newInsertionRegister.Measurement)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
}
// Phone number string length:
length = len(newInsertionRegister.Phone)
if length!=9 {
fmt.Println("Invalid Phone string length: ", newInsertionRegister.Phone)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
}
// Correct phone number
re := regexp.MustCompile(`^(?:(?:\(?(?:00|\+)([1-4]\d\d|[1-9]\d?)\)?)?[\-\.\ \\\/]?)?((?:\(?\d{1,}\)?[\-\.\ \\\/]?){0,})(?:[\-\.\ \\\/]?(?:#|ext\.?|extension|x)[\-\.\ \\\/]?(\d+))?$`)
if !re.MatchString(newInsertionRegister.Phone) {
fmt.Println("Phone number is not valid: ", newInsertionRegister.Phone)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
}
// Project string length:
length = len(newInsertionRegister.Project)
if length<1 || length>=50 {
fmt.Println("Invalid Project string length: ", newInsertionRegister.Project)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
}
// Group string length:
length = len(newInsertionRegister.Group)
if length<1 || length>=50 {
fmt.Println("Invalid Group string length: ", newInsertionRegister.Group)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
}
// Spam value:
if newInsertionRegister.Spam != "true" && newInsertionRegister.Spam != "True" && newInsertionRegister.Spam != "TRUE" && newInsertionRegister.Spam != "false" && newInsertionRegister.Spam != "False" && newInsertionRegister.Spam != "FALSE" {
fmt.Println("Invalid Spam value: ", newInsertionRegister.Spam)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
} else {
// Ensure that data is saved in lowercase
newInsertionRegister.Spam = strings.ToLower(newInsertionRegister.Spam)
}
// Google value:
if newInsertionRegister.Google != "true" && newInsertionRegister.Google != "True" && newInsertionRegister.Google != "TRUE" && newInsertionRegister.Google != "false" && newInsertionRegister.Google != "False" && newInsertionRegister.Google != "FALSE" {
fmt.Println("Invalid Google value: ", newInsertionRegister.Google)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
} else {
// Ensure that data is saved in lowercase
newInsertionRegister.Google = strings.ToLower(newInsertionRegister.Google)
}
// ShouldAnswer value:
if newInsertionRegister.ShouldAnswer != "true" && newInsertionRegister.ShouldAnswer != "True" && newInsertionRegister.ShouldAnswer != "TRUE" && newInsertionRegister.ShouldAnswer != "false" && newInsertionRegister.ShouldAnswer != "False" && newInsertionRegister.ShouldAnswer != "FALSE" {
fmt.Println("Invalid ShouldAnswer value: ", newInsertionRegister.ShouldAnswer)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusBadRequest, "Incorrect JSON")
return
} else {
// Ensure that data is saved in lowercase
newInsertionRegister.ShouldAnswer = strings.ToLower(newInsertionRegister.ShouldAnswer)
}
}
//spew.Dump(newInsertionRegister)
// Insert received data to InfluxDB
bucket := "kr0m-bucket"
org := "ALFAEXPLOIT"
token := "YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY"
url := "http://localhost:8086"
client := influxdb2.NewClient(url, token)
writeAPI := client.WriteAPIBlocking(org, bucket)
tags := map[string]string{
// Low cardinality data->tag
"Project": newInsertionRegister.Project,
"Group": newInsertionRegister.Group,
"Spam": newInsertionRegister.Spam,
"Google": newInsertionRegister.Google,
"ShouldAnswer": newInsertionRegister.ShouldAnswer,
}
fields := map[string]interface{}{
// High cardinallityt data -> Field
"Phone": newInsertionRegister.Phone,
}
point := write.NewPoint(newInsertionRegister.Measurement, tags, fields, time.Now())
if err := writeAPI.WritePoint(context.Background(), point); err != nil {
fmt.Println("Error inserting data to InfluDB:", err)
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(500, gin.H{"error": "Guru mediation error"})
return
}
client.Close()
c.Writer.Header().Set("X-Author", "kr0m-AlfaExploit")
c.JSON(http.StatusOK, "Insertion successfully")
}
Tan solo recordar configurar el gestor de certificados para que reinicie la API cada vez que el certificado sea renovado.
Dejo los tests unitarios por si resultan de utilidad:
package main
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
)
// AUTH:
func TestAuthWrong(t *testing.T) {
fmt.Println("Testing: TestAuthWrong")
router := setUpRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 401, w.Code)
}
// ROUTES:
func TestRandomRoute(t *testing.T) {
fmt.Println("Testing: TestRandomRoute")
router := setUpRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("GET", "/randompath", nil)
router.ServeHTTP(w, req)
assert.Equal(t, 404, w.Code)
}
// MEASUREMENT TESTS:
func TestNullMeasurement(t *testing.T) {
fmt.Println("Testing: TestNullMeasurement")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongMeasurement(t *testing.T) {
fmt.Println("Testing: TestWrongMeasurement")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"111111111122222222223333333333444444444455555555556", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// PHONE TESTS:
func TestNullPhone(t *testing.T) {
fmt.Println("Testing: TestNullPhone")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongPhoneLength(t *testing.T) {
fmt.Println("Testing: TestWrongPhoneLength")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "111111111122222222223333333333444444444455555555556", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongPhone(t *testing.T) {
fmt.Println("Testing: TestWrongPhone")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "1234455AA", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// PROJECT TESTS:
func TestNullProject(t *testing.T) {
fmt.Println("Testing: TestNullProject")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongProject(t *testing.T) {
fmt.Println("Testing: TestWrongProject")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "111111111122222222223333333333444444444455555555556", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// GROUP TESTS:
func TestNullGroup(t *testing.T) {
fmt.Println("Testing: TestNullGroup")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongGroup(t *testing.T) {
fmt.Println("Testing: TestWrongGroup")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "111111111122222222223333333333444444444455555555556", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// SPAM TESTS:
func TestNullSpam(t *testing.T) {
fmt.Println("Testing: TestNullSpam")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongSpam(t *testing.T) {
fmt.Println("Testing: TestWrongSpam")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "kr0m", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// GOOGLE TESTS:
func TestNullGoogle(t *testing.T) {
fmt.Println("Testing: TestNullSpam")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongGoogle(t *testing.T) {
fmt.Println("Testing: TestWrongSpam")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "kr0m", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// SHOULDANSWER TESTS:
func TestNullShouldAnswer(t *testing.T) {
fmt.Println("Testing: TestNullShouldAnswer")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": ""}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
func TestWrongShouldAnswer(t *testing.T) {
fmt.Println("Testing: TestWrongShouldAnswer")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "kr0m"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 400-BadRequest
assert.Equal(t, http.StatusBadRequest, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Incorrect JSON\"", w.Body.String())
}
// ALL DATA CORRECT TEST:
func TestCorrectData(t *testing.T) {
fmt.Println("Testing: TestCorrectData")
router := setUpRouter()
var jsonStr = []byte(`{"Measurement":"kr0mMeasurement", "Phone": "123445566", "Project": "kr0mProject", "Group": "kr0mGroup", "Spam": "false", "Google": "false", "ShouldAnswer": "true"}`)
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/insertdata", bytes.NewBuffer(jsonStr))
req.Header.Set("Content-Type", "application/json")
req.SetBasicAuth("kr0m", "XXXXXX")
router.ServeHTTP(w, req)
// Check response HTTP code: 200-OK
assert.Equal(t, http.StatusOK, w.Code)
// Check JSON string:
//fmt.Println("Response:", w.Body.String())
assert.Equal(t, "\"Insertion successfully\"", w.Body.String())
}