Implementing IP restrictions in Go gin - go

I'm setting up a small demo app I'd like only accessible from my home IP address for now, and maybe a small set of technical people I'll coordinate and share with.
I looked through the readme here, but couldn't find:
https://github.com/gin-gonic/gin
---what's the canonical, minimal example for how to limit access on an app to only particular IP addresses in gin?
(Also, any reason this is a particularly unsafe idea in 2018?)

Before I answer your question, I would like to say that it would likely be more practical to limit access to the app using firewall rules rather than in the program itself, but I digress.
To answer your question, after looking through the gin godoc reference I found that the context struct contains a ClientIp() method that:
implements a best effort algorithm to return the real client IP, it parses X-Real-IP and X-Forwarded-For in order to work properly with reverse-proxies such us: nginx or haproxy. Use X-Forwarded-For before X-Real-Ip as nginx uses X-Real-Ip with the proxy's IP.
Therefore, if you are set on doing the IP filtering in the app, you could filter based on the value returned by that method.
Using the basic example given on the Github page:
package main
import "github.com/gin-gonic/gin"
var Whitelist []string = []string{"1.2.3.4"}
func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
whitelisted := false
for _, v := range Whitelist {
if v == c.ClientIP() {
whitelisted = true
}
}
if whitelisted {
c.JSON(200, gin.H{
"message": "pong",
})
} else {
c.JSON(403, gin.H{})
}
})
r.Run() // listen and serve on 0.0.0.0:8080
}

Related

How to change which IP address a go-fiber client is using?

I'm using Fiber as an HTTP client to make some requests to an http server, however I'm being rate limited. On my vm I configured 5 different IP addresses (public/private) and have confirmed that they are indeed connected to the internet.
curl --interface 10.0.0.4 ipinfo.io/json
curl --interface 10.0.0.5 ipinfo.io/json
...
curl --interface 10.0.0.8 ipinfo.io/json
each one returns a different public facing ip address.
Now I'm interested in making round-robin requests using these local addresses but I'm not so sure how to go about it.
Is there some sort of property or function I can set/call to change where the outgoing request is coming from?
I've looked around at fasthttp.HostClient which fiber.Agent extends but I didn't see anything useful.
Thanks guys.
a := fiber.AcquireAgent()
req := a.Request()
req.Header.SetMethod(fiber.MethodGet)
req.SetRequestURI(fmt.Sprintf(formatUrl, args...))
if err := a.Parse(); err != nil {
h.Logger.Error("%v", err)
return fiber.StatusInternalServerError, nil, []error{err}
}
customDialer := fasthttp.TCPDialer{
Concurrency: 1000,
LocalAddr: &net.TCPAddr{
IP: h.IPPool[atomic.AddUint32(&h.IpIdx, 1)%uint32(len(h.IPPool))],
},
}
a.HostClient.Dial = func(addr string) (net.Conn, error) {
return customDialer.Dial(addr)
}
Creating a custom dialer and dial func allows you to change the local address associated with the http request.

How to get full server URL from any endpoint handler in Gin

I'm creating an endpoint using Go's Gin web framework. I need full server URL in my handler function. For example, if server is running on http://localhost:8080 and my endpoint is /foo then I need http://localhost:8080/foo when my handler is called.
If anyone is familiar with Python's fast API, the Request object has a method url_for(<endpoint_name>) which has the exact same functionality: https://stackoverflow.com/a/63682957/5353128
In Go, I've tried accessing context.FullPath() but that only returns my endpoint /foo and not the full URL. Other than this, I can't find appropriate method in docs: https://pkg.go.dev/github.com/gin-gonic/gin#Context
So is this possible via gin.Context object itself or are there other ways as well? I'm completely new to Go.
c.Request.Host+c.Request.URL.Path should work but the scheme has to be determined.
package main
import (
"fmt"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/foo", func(c *gin.Context) {
fmt.Println("The URL: ", c.Request.Host+c.Request.URL.Path)
})
r.Run(":8080")
}
You can determine scheme which also you may know already. But you can check as follows:
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
If your server is behind the proxy, you can get the scheme by c.Request.Header.Get("X-Forwarded-Proto")
You can get host part localhost:8080 from context.Request.Host and path part /foo from context.Request.URL.String().
package main
import (
"fmt"
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/foo", func(c *gin.Context) {
c.String(http.StatusOK, "bar")
fmt.Println(c.Request.Host+c.Request.URL.String())
})
// Listen and Server in 0.0.0.0:8080
r.Run(":8080")
}
And you can get http protocol version by context.Request.Proto, But it will not determine http or https. you need to get it from your service specifications.

How to send same cookies (CookieJar) in http.Client for different domains

I'm using http.Client for making HTTP requests for some production resource.
This resource has two different domains with the same business-logic
(for example: example.com, instance.com). So ALL cookies for example.com is valid for instance.com and so.
The problem is that I need to send same cookies to two different domains, that is not possible in GoLang.
func (*Jar) Cookies returns cookies for url with a specific domain, so I must call some cookies-preparation function:
func (session *Session) PrepareCookiesForExample() {
example, _ := url.Parse("https://example.com")
session.client.Jar.SetCookies(example, session.client.Jar.Cookies(commu))
}
So I have to call this function in my each request that is pretty uncomfortable and can cause errors (because cookies are not sent) if I forget to call this fuction.
How to send the same cookies for ALL domains by using CookieJar?
First of all, a reminder that restricting cookies to the domains they were set from is an important security feature that should not be bypassed lightly.
Here is an example of how you'd create your own cookie Jar:
package main
import (
"net/http"
"net/url"
)
type SharedCookieJar struct {
CookieSlice []*http.Cookie
}
func (jar *SharedCookieJar) SetCookies(u *url.URL, cookies []*http.Cookie) {
jar.CookieSlice = append(jar.CookieSlice, cookies...)
}
func (jar *SharedCookieJar) Cookies(u *url.URL) []*http.Cookie {
return jar.CookieSlice
}
func main() {
c := http.Client{
Jar:&SharedCookieJar{},
}
c.Get("https://example.com/")
c.Get("https://instance.com/") // will use cookies set by example.com
}
Further reading on interfaces here: https://tour.golang.org/methods/9

How to end to end/integration test a Go app that use a reverse proxy to manage subdomain?

I have a Go app that use Gin gonic and a Nginx reverse proxy that send trafic to another app on domain.com and send all the *.domain.com subdomains traffic directly to my go app.
My Go app then has a middleware that will read the hostname that nginx passes to it from Context and allow my handlers to know what subdomain is being request and return the proper data and cookies for said subdomain.
It's a pretty simple setup and it seems to work fine from my test in postman as all my routes are the same across all my subdomains so this way i can only use one router for all of them instead of one router per subodmain.
Now my big problem come when i'm trying to do end to end testing.
I'm setting up my test like this :
router := initRouter()
w := httptest.NewRecorder()
req, _ := http.NewRequest("POST", "/api/login", bytes.NewBuffer(jsonLogin))
req.Header.Set("Content-Type", "application/json")
router.ServeHTTP(w, req)
assert.Equal(t, 200, w.Code)
with initRouter() returning a gin engine with all my routes and middlewares loaded and the rest as a basic test setup.
Obviously the test will fail as the gin Context won't ever receive a subdomain from context and act as if everything is coming from localhost:8000.
Is there a way to either :
"Mock" a subdomain so that the router think the call is coming from foo.localhost.com instead of localhost
Setup my test suit so that the test request are routed thought nginx.. i'd prefer solution 1 as this would be a mess to setup / maintain.
Edit :
As per the httptest doc i've tried to hard code foo.localhost as the param of the NewRequest but it doesn't really behave as i need it to behave :
NewRequest returns a new incoming server Request, suitable for passing to an http.Handler for testing.
The target is the RFC 7230 "request-target": it may be either a path or an absolute URL. If target is an absolute URL, the host name from the URL is used. Otherwise, "example.com" is used.
When hardcoding http://foo.localhost.com/api/login or foo.localhost.com/api/login as the request target it directly passes it to my router under "foo.localhost.com/api/login" while nginx would just hit the /api/login directly and parse from c.Request.Host
Edit 2:
I'm currently exploring setting the host manually using :
req.Header.Set("Host", "foo.localhost")
The request returned by http.NewRequest isn't suitable for passing directly to ServeHTTP. Use one returned by httptest.NewRequest instead.
Simply set the Host field directly:
package main
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHelloWorld(t *testing.T) {
mux := http.NewServeMux()
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if r.Host != "foobar" {
t.Errorf("Host is %q, want foobar", r.Host)
}
})
w := httptest.NewRecorder()
r := httptest.NewRequest("GET", "/api/login", nil)
r.Host = "foobar"
mux.ServeHTTP(w, r)
}

How can I get the client IP address and user-agent in Golang gRPC?

I set up a series of gRPC requests and responses which all work fine, but I'm stuck when I try to get the client IP address and user-agent who is calling my gRPC APIs.
I read the Go gRPC documentation and other sources, but didn't find much valuable information. Few of them are talking about gRPC in Golang.
Should I set up a key-value to store the IP address in the context when setting up the gRPC APIs?
In Golang GRPC, you can use
func (UserServicesServer) Login(ctx context.Context, request *sso.LoginRequest) (*sso.LoginResponse, error) {
p, _ := peer.FromContext(ctx)
request.Frontendip = p.Addr.String()
.
.
}
But, do not forget import "google.golang.org/grpc/peer"
For grpc-gateway is used, the client IP address may be retrieved through x-forwarded-for like this:
// Get IP from GRPC context
func GetIP(ctx context.Context) string {
if headers, ok := metadata.FromIncomingContext(ctx); ok {
xForwardFor := headers.Get("x-forwarded-for")
if len(xForwardFor) > 0 && xForwardFor[0] != "" {
ips := strings.Split(xForwardFor[0], ",")
if len(ips) > 0 {
clientIp := ips[0]
return clientIp
}
}
}
return ""
}
In Golang GRPC, context has 3 values
authority
content-type
user-agent
md,ok:=metadata.FromIncomingContext(ctx)
fmt.Printf("%+v%+v",md,ok)

Resources