Develop a Secured Restful API in Golang

Issue a certificate using Let’s Encrypt

https://hainguyen.dev/archives/secure-wep-app-using-lets-encrypt-ssl/

Serve a ssl server with Golang using Gin

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
package main

import (
	"github.com/gin-gonic/gin"
)

func main() {
	g := gin.Default()

	g.GET("/ping/", func(c *gin.Context) {
		c.String(200, "Pong")
	})
	
	g.RunTLS(":3000", "/etc/letsencrypt/live/api-dev.hainguyen.dev/fullchain.pem", "/etc/letsencrypt/live/api-dev.hainguyen.dev/privkey.pem")
}

Create a bash script to run the go app:

1
2
3
cd api
touch run.sh
chmod +x run.sh

Add the following content to the run.sh file:

1
2
3
4
5
6
7
#!/bin/bash

go build -o server main.go

kill -9 $(lsof -t -i:3000)

nohup ./server & disown

Note: The kill command is used to kill the process running on port 3000. If you are using a different port, change the port number in the command. The nohup command is used to run the application in the background. The disown command is used to detach the process from the current shell.

Run the script to start the server:

1
./run.sh

Add CORS middleware

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
func CORSMiddleware(allowedOrigins []string) gin.HandlerFunc {
    return func(c *gin.Context) {
        c.Writer.Header().Set("Access-Control-Allow-Origin", allowedOrigins)
        c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
        c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")
        c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT")
    
        if c.Request.Method == "OPTIONS" {
            c.AbortWithStatus(204)
            return
        }
    
        c.Next()
    }
}

Modify the main function to use the middleware:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
func main() {
    g := gin.Default()
	
	origins := []string{"https://hainguyen.dev", "https://api-dev.hainguyen.dev"}
    g.Use(CORSMiddleware(origins))

    g.GET("/ping/", func(c *gin.Context) {
        c.String(200, "Pong")
    })
    
    g.RunTLS(":3000", "/etc/letsencrypt/live/api-dev.hainguyen.dev/fullchain.pem", "/etc/letsencrypt/live/api-dev.hainguyen.dev/privkey.pem")
}

Troubleshooting

There may be a permission issue when running the app needs to access the certificate files. To fix this, run the following command:

1
2
sudo chmod 755 /etc/letsencrypt/live/api-dev.hainguyen.dev/
sudo chmod 755 /etc/letsencrypt/live/api-dev.hainguyen.dev/

Setup Nginx to serve the app via a domain

Edit the Nginx configuration file:

1
sudo vi /etc/nginx/con.d/api-dev.hainguyen.dev.conf
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# HTTP - require all requests must be over HTTPS
server {
    listen 80;
    listen [::]:80;
    server_name api-dev.hainguyen.dev;

    location / {
        proxy_pass http://localhost:8068;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

server {
    listen 443 ssl;

    server_name api-dev.hainguyen.dev;

    # SSL
    ssl_certificate /etc/letsencrypt/live/api-dev.hainguyen.dev/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api-dev.hainguyen.dev/privkey.pem;
    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
    ssl_prefer_server_ciphers on;
    ssl_ciphers EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;

    # Improve HTTPS performance with session resumption
    ssl_session_cache shared:SSL:50m;
    ssl_session_timeout 1d;

    # DH parameters
    ssl_dhparam /etc/nginx/ssl/dhparam.pem;
    # Enable HSTS
    add_header Strict-Transport-Security "max-age=31536000" always;

    location / {
        proxy_pass https://localhost:8086;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_set_header Host $host;
        proxy_cache_bypass $http_upgrade;
    }
}

Restart Nginx:

1
sudo service nginx restart

Verify the server

1
curl -k https://<domain_name>/ping/

Troubleshooting

If you get the following error: 502 Bad Gateway (nginx)

This may be because the the httpd network connect is not allowed (by default, on SELinux). To fix this, run the following command:

1
setsebool -P httpd_can_network_connect true

Don’t have permission to access the certificate files. To fix this, run the following command:

/etc/letsencrypt/live/<domain_name>/fullchain.pem: permission denied

1
2
3
4
5

fix the permission issue:
```shell
sudo chmod 755 /etc/letsencrypt/live/<domain_name>/
sudo chown <linux_user> -R /etc/letsencrypt/live/domain_name

To find your linux user, run the following command:

1
whoami