Created
October 12, 2021 17:47
-
-
Save ezr/c2ea73bf189fb595c17110e172309e8a to your computer and use it in GitHub Desktop.
Google cloud function to scan a given IP/TCP port.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uses a shared secret.
The client passes an "Api-Key" HTTP header.
The server looks for an environment variable called "PORTCHECKKEY".