Esta pagina se ve mejor con JavaScript habilitado

API REST InfluxDB en Go con auth y SSL

 ·  🎃 kr0m

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())
}
Si te ha gustado el artículo puedes invitarme a un RedBull aquí