Skip to content

Instantly share code, notes, and snippets.

@ezr
Created October 12, 2021 17:47
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ezr/c2ea73bf189fb595c17110e172309e8a to your computer and use it in GitHub Desktop.
Save ezr/c2ea73bf189fb595c17110e172309e8a to your computer and use it in GitHub Desktop.
Google cloud function to scan a given IP/TCP port.
package portchecker
/* A Google cloud function that takes an address and a TCP port.
It scans the port and tells you whether its open or not.
If "addr" is not supplied, then it scan the requestor.
Example usage:
$ curl --header "Api-Key: $API_KEY" --data "port=443&addr=142.250.65.174" https://us-region-projectname.cloudfunctions.net/portchecker
*/
import (
"crypto/subtle"
"errors"
"fmt"
"log"
"net"
"net/http"
"os"
"strconv"
"strings"
"time"
)
func checkPortValid(port string) bool {
p, err := strconv.Atoi(port)
if err != nil {
return false
}
if (p < 1) || (p > 65535) {
return false
} else {
return true
}
}
func extractAddr(a string) string {
// "198.51.100.1:80" or "[2001:db8::1]:53" as input
// will return just the address (without brackets for IPv6)
if strings.Count(a, ":") > 1 {
// then its an IPv6 address
return strings.TrimPrefix(strings.Split(a, "]")[0], "[")
} else {
return strings.Split(a, ":")[0]
}
}
func checkPort(addr string, port string) bool {
timeout, err := time.ParseDuration("4s")
if err != nil {
log.Fatal(err)
}
/* DialTimeout's second argument should be of the form:
"198.51.100.1:80" or "[2001:db8::1]:53"
*/
conn, err := net.DialTimeout("tcp", addr+":"+port, timeout)
if err != nil {
log.Println(err)
return false
}
defer conn.Close()
return true
}
func checkAuth(key string) error {
expectedKey := os.Getenv("PORTCHECKKEY")
if expectedKey == "" {
return errors.New("API key environment variable not set")
} else if len(key) == 0 {
return errors.New("API key not provided")
}
cmp := subtle.ConstantTimeCompare([]byte(expectedKey), []byte(key))
// ConstantTimeCompare returns 1 if the two slices have equal contents and 0 otherwise
if cmp == 1 {
return nil
} else {
return errors.New("bad key")
}
}
func ScanServer(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
fmt.Fprintf(w, "only post is supported\n")
return
}
err := checkAuth(r.Header.Get("Api-Key"))
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("401 - unauthorized"))
return
}
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
port := r.FormValue("port")
if port == "" {
fmt.Fprintf(w, "error - you must supply a port\n")
return
} else if !checkPortValid(port) {
fmt.Fprintf(w, "error - invalid port\n")
return
}
addr := r.FormValue("addr")
// if addr is not supplied, then use the address the request originated from
if addr == "" {
xff := r.Header.Get("X-Forwarded-For")
/* A comma-delimited list of IP addresses through which the client request has been routed. The first IP in this list is generally the IP of the client that created the request. The subsequent IPs provide information about proxy servers that also handled the request before it reached the application server. */
addr = strings.Split(xff, ",")[0]
}
portIsOpen := checkPort(addr, port)
if portIsOpen {
fmt.Fprintf(w, addr+":"+port+" open\n")
} else {
fmt.Fprintf(w, addr+":"+port+" not open\n")
}
}
@ezr
Copy link
Author

ezr commented Oct 12, 2021

Uses a shared secret.
The client passes an "Api-Key" HTTP header.
The server looks for an environment variable called "PORTCHECKKEY".

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment