func main() { enableTls, port, certFile, keyFile, chainHandler := parseFlags() if connections.RedisPool != nil { defer connections.RedisPool.Close() } if *enableTls { // serve over https // if you want to test this locally, generate a cert and key file using // go by running a command similar to : // "go run /usr/local/go/src/crypto/tls/generate_cert.go --host localhost" // and change the location to wherever go is installed on your system err := http.ListenAndServeTLS(*port, *certFile, *keyFile, chainHandler) if err != nil { log.Fatal(err) } } else { // serve over http (non-secure) glog.Infof("WARNING: Running over plaintext http (insecure). " + "To enable https, use '-enable-tls'") http.ListenAndServe(*port, chainHandler) } }
func storeRequest(r *http.Request) { ip := connections.FindIp(r) resource := r.URL.Path key := lastSerialized.String() + DELIMETER + ip + DELIMETER + resource mutex.Lock() // increment count of times this ip/resource pair has been seen userResourceCounts[key] = userResourceCounts[key] + 1 mutex.Unlock() runtime.Gosched() // if we hit the serialization time .. if time.Now().After(lastSerialized.Add(serializationDuration)) { glog.Infof("Purging now - comparing %s + ms (%s) <--> %s", lastSerialized.String(), serializationDuration.String(), lastSerialized.Add(serializationDuration).String(), time.Now()) serializedTimestampString := lastSerialized.String() // update last serialized for next batch lastSerialized = time.Now() flushToRedis(serializedTimestampString) } }
func RefreshBlocks(blockRefreshUrl *string) { //"http://localhost:8090/api/v1.0/blocks" _, body, _ := gorequest.New().Get(*blockRefreshUrl).End() var blocks Blocks if err := json.Unmarshal([]byte(body), &blocks); err != nil { panic(err) } for _, element := range blocks { // temporary hack for dealing w/ java block store // it keeps timestamp in microseconds for some reason element.Endtime = element.Endtime / 1000 t, _ := unixToTime(element.Endtime) // only add if this is still a valid time block if time.Now().Before(t) { //back to json so it's hashable jsonStr, _ := json.Marshal(element) StoredBlocks.Add(string(jsonStr)) } } // glog.Info(StoredBlocks.Len()) glog.Infof("Retrieved %d blocks, total stored: %d", len(blocks), StoredBlocks.Len()) // glog.Info("STORED blocks: ", StoredBlocks.Flatten()) // glog.Info("Printing response: ", resp) // glog.Info("Printing body: ", body) }
func ExpireBlocks() { glog.Info("Evaluating blocks for expiration.") var removableBlocks []string for _, element := range StoredBlocks.Flatten() { var block Block //glog.Info(element) if err := json.Unmarshal([]byte(element.(string)), &block); err != nil { panic(err) } t, err := unixToTime(block.Endtime) if err != nil { glog.Fatal(err) } if time.Now().After(t) { glog.Info("Expiring block: ", block) removableBlocks = append(removableBlocks, element.(string)) } else { glog.Infof("Not expiring block:") glog.Infof("\tnow: ", time.Now()) glog.Infof("\tt: ", t) glog.Infof("\tnow unix: ", time.Now().Unix()) glog.Infof("\tt unix: ", t.Unix()) } } if len(removableBlocks) > 0 { for _, removableBlock := range removableBlocks { StoredBlocks.Remove(removableBlock) } } }
func ConnectRedis(redisAddress *string, maxRedisConnections *int) { glog.Infof("Attempting redis connection at %s with %d connections", *redisAddress, *maxRedisConnections) RedisPool = redis.NewPool(func() (redis.Conn, error) { c, err := redis.Dial("dtcp", *redisAddress) if err != nil { glog.Fatal("redis connection could not be made") return nil, err } return c, err }, *maxRedisConnections) }
// from https://justinas.org/writing-http-middleware-in-go/ func LogExecutionTime(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { start := time.Now() rec := httptest.NewRecorder() // passing a ResponseRecorder instead of the original RW next.ServeHTTP(rec, r) // we copy the original headers first for k, v := range rec.Header() { w.Header()[k] = v } w.WriteHeader(rec.Code) // then write out the original body w.Write(rec.Body.Bytes()) glog.Infof("%v %s on %s %s in %v", rec.Code, http.StatusText(rec.Code), r.Method, r.URL.Path, time.Since(start)) }) }
func parseFlags() (*bool, *string, *string, *string, http.Handler) { const ( defaultPort = ":8080" defaultPortUsage = "default server port, ':80', ':8080'..." defaultTarget = "http://127.0.0.1:8090" defaultTargetUsage = "default redirect url, 'http://127.0.0.1:8080'" defaultBooleanUsage = "true or false" defaultRefreshUsage = "number of seconds between block refreshes" defaultExpireUsage = "number of seconds between expiration sweeps" defaultBlocksRefreshUrl = "block refresh url, 'http://localhost:8090/api/v1.0/blocks'" defaultCertFile = "cert file (e.g. cert.pem)" defaultKeyFile = "key file (e.g. key.pem)" defaultResourceVerbsMappingFile = "resource verbs mapping file (e.g. resource-verbs-mapping.yml)" defaultResourcesFile = "resources file (e.g. resources.yml)" ) // flags port := flag.String("port", defaultPort, defaultPortUsage) url := flag.String("url", defaultTarget, defaultTargetUsage) // for running in TLS mode enableTls := flag.Bool("enable-tls", false, defaultBooleanUsage) certFile := flag.String("cert-file", "cert.pem", defaultCertFile) keyFile := flag.String("key-file", "key.pem", defaultKeyFile) // related to trend detection enableTrends := flag.Bool("enable-trend-tracking", false, defaultBooleanUsage) // related to blocking enableBlocking := flag.Bool("enable-blocking", false, defaultBooleanUsage) refreshSeconds := flag.Int("blocking-refresh-rate-seconds", 30, defaultRefreshUsage) expireSeconds := flag.Int("blocking-expire-rate-seconds", 30, defaultExpireUsage) blocksRefreshUrl := flag.String("blocking-blocks-refresh-url", "http://localhost", defaultBlocksRefreshUrl) // for redis, if used redisAddress := flag.String("redis-address", ":6379", "Address to the Redis server") maxRedisConnections := flag.Int("max-redis-connections", 10, "Max connections to Redis") // related to invalid http verbs // (instead of GET, POST, HEAD ... you receive DOG, SHOE, FOREST, etc.) enableInvalidVerbs := flag.Bool("enable-invalid-verb-checking", false, defaultBooleanUsage) // related to unexpected http verbs // requires config file // config file states this file reachable over GET or HEAD, and you get a POST enableUnexpectedVerbs := flag.Bool("enable-unexpected-verb-checking", false, defaultBooleanUsage) resourceVerbsMappingFile := flag.String("resource-verbs-mapping-file", "resource-verbs-mapping.yml", defaultResourceVerbsMappingFile) // related to unexpected resources // requires config file // config file states all resources expected in the app (think site-map) // this analysis should help detect scanners probing for non-existent resources enableUnexpectedResources := flag.Bool("enable-unexpected-resource-checking", false, defaultBooleanUsage) resourcesFile := flag.String("resources-file", "resources.yml", defaultResourcesFile) flag.Parse() glog.Info("------------------------------------------------") glog.Infof("Settings:") glog.Infof("\tServer port %s", *port) glog.Infof("\tProxy url: %s", *url) glog.Infof("\tEnable TLS: %t", *enableTls) glog.Infof("\t\tCert File: %s", *certFile) glog.Infof("\t\tKey File: %s", *keyFile) glog.Infof("\tEnable trend tracking: %t", *enableTrends) glog.Infof("\tEnable blocking: %t", *enableBlocking) glog.Infof("\t\tRefresh rate (seconds): %d", *refreshSeconds) glog.Infof("\t\tExpire rate (seconds): %d", *expireSeconds) glog.Infof("\t\tBlock refresh url: %s", *blocksRefreshUrl) glog.Infof("\tEnable invalid verbs: %t", *enableInvalidVerbs) glog.Infof("\tEnable unexpected verbs: %t", *enableUnexpectedVerbs) glog.Infof("\t\tResource verbs mapping file: %s", *resourceVerbsMappingFile) glog.Infof("\tEnable unexpected resources: %t", *enableUnexpectedResources) glog.Infof("\t\tResources file: %s", *resourcesFile) glog.Info("------------------------------------------------") // proxy proxy := New(*url) proxyHandler := http.HandlerFunc(proxy.handle) // chain default handlers (clear context, recovery, and log execution time) chain := alice.New( context.ClearHandler, middleware.Recovery, middleware.LogExecutionTime) // if blocking is enabled, add it to alice chain if *enableBlocking { chain = chain.Append(middleware.Block) go doEvery((time.Duration(*refreshSeconds) * time.Second), func() { blocks.RefreshBlocks(blocksRefreshUrl) }) go doEvery((time.Duration(*expireSeconds) * time.Second), blocks.ExpireBlocks) } // if trending is enabled, add it to alice chain if *enableTrends { chain = chain.Append(middleware.Trend) } // if invalid verb checking is enabled, add it to alice chain if *enableInvalidVerbs { chain = chain.Append(middleware.InvalidVerbs) } // if unexpected verb checking is enabled, add it to alice chain if *enableUnexpectedVerbs { middleware.PopulateExpectedVerbs(resourceVerbsMappingFile) chain = chain.Append(middleware.UnexpectedVerbs) } // if unexpected resource checking is enabled, add it to alice chain if *enableUnexpectedResources { middleware.PopulateExpectedResources(resourcesFile) chain = chain.Append(middleware.UnexpectedResources) } // if any of these settings are enabled, connect to redis if *enableTrends { connections.ConnectRedis(redisAddress, maxRedisConnections) } // if any of these settings are enabled, setup rest client to appsensor backend if *enableInvalidVerbs || *enableTrends || *enableUnexpectedVerbs || *enableUnexpectedResources { ids.InitializeAppSensorRestClient() } // always use reverse proxy as chain target (final) chainHandler := chain.Then(proxyHandler) return enableTls, port, certFile, keyFile, chainHandler }