1
0
Fork 0

feature: documentation, category enum and cached check-block

This commit is contained in:
Seraphim Strub 2024-08-08 17:57:27 +00:00
parent d456ceac98
commit 4f1fe4840e

View file

@ -27,6 +27,45 @@ const (
defaultUserAgent = "go-abuseipdb/" + Version defaultUserAgent = "go-abuseipdb/" + Version
) )
const (
endpointCheck = "check"
endpointReports = "reports"
endpointBlacklist = "blacklist"
endpointReport = "report"
endpointCheckBlock = "check-block"
endpointBulkReport = "bulk-report"
endpointClearAddress = "clear-address"
)
// ReportCategory is documented at https://www.abuseipdb.com/categories
type ReportCategory int
const (
DnsCompromise ReportCategory = iota + 1
DnsPoisoning
FraudOrders
DDoSAttack
FtpBruteForce
PingOfDeath
Phishing
FraudVoIp
OpenProxy
WebSpam
EmailSpam
BlogSpam
VpnIp
PortScan
Hacking
SqlInjection
Spoofing
BruteForce
BadWebBot
ExploitedHost
WebAppAttack
Ssh
IotTargeted
)
type Client struct { type Client struct {
client *http.Client client *http.Client
BaseURL *url.URL BaseURL *url.URL
@ -51,6 +90,7 @@ func NewClient(client *http.Client) *Client {
return c return c
} }
// AddApiKey pass api key from https://www.abuseipdb.com/account/api
func (c *Client) AddApiKey(key string) { func (c *Client) AddApiKey(key string) {
c.APIKey = key c.APIKey = key
} }
@ -126,6 +166,9 @@ func (c *Client) do(ctx context.Context, req *http.Request, v any) error {
return err return err
} }
defer resp.Body.Close() defer resp.Body.Close()
if err := handleError(resp); err != nil {
return err
}
switch v := v.(type) { switch v := v.(type) {
case nil: case nil:
@ -172,14 +215,13 @@ type CheckResult struct {
type CheckResultReport struct { type CheckResultReport struct {
ReportedAt time.Time `json:"reportedAt"` ReportedAt time.Time `json:"reportedAt"`
Comment string `json:"comment"` Comment string `json:"comment"`
Categories []int `json:"categories"` Categories []ReportCategory `json:"categories"`
ReporterId int `json:"reporterId"` ReporterId int `json:"reporterId"`
ReporterCountryCode string `json:"reporterCountryCode"` ReporterCountryCode string `json:"reporterCountryCode"`
ReporterCountryName string `json:"reporterCountryName"` ReporterCountryName string `json:"reporterCountryName"`
} }
var checkEndpoint = "check" // Check an IP for abuse and additional information
func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) { func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) {
ipAddress := html.EscapeString(ip.String()) ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{ parameters := map[string]string{
@ -195,7 +237,7 @@ func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*Che
} }
} }
req, err := c.newRequest(http.MethodGet, checkEndpoint, parameters, nil) req, err := c.newRequest(http.MethodGet, endpointCheck, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -208,13 +250,15 @@ func (c *Client) Check(ctx context.Context, ip net.IP, opts *CheckOptions) (*Che
return &result, nil return &result, nil
} }
// CheckCached uses the client cache and the ip as a key // CheckCached same as Check but uses the client cache and the ip as a key (cache has to be added to client for this to work)
func (c *Client) CheckCached(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) { func (c *Client) CheckCached(ctx context.Context, ip net.IP, opts *CheckOptions) (*CheckResult, error) {
key := fmt.Sprintf("%s:%s", checkEndpoint, ip.String()) if c.cache == nil {
return nil, errors.New("cache is missing")
}
key := fmt.Sprintf("%s:%s", endpointCheck, ip.String())
if r, err := c.cache.Get(key); err == nil { if r, err := c.cache.Get(key); err == nil {
result := CheckResult{} result := CheckResult{}
if jsonErr := json.NewDecoder(bytes.NewReader(r)).Decode(&result); jsonErr == nil { if jsonErr := json.NewDecoder(bytes.NewReader(r)).Decode(&result); jsonErr == nil {
fmt.Println("return cached")
return &result, nil return &result, nil
} }
} }
@ -253,14 +297,14 @@ type ReportsResult struct {
type ReportResultReport struct { type ReportResultReport struct {
ReportedAt time.Time `json:"reportedAt"` ReportedAt time.Time `json:"reportedAt"`
Comment string `json:"comment"` Comment string `json:"comment"`
Categories []int `json:"categories"` Categories []ReportCategory `json:"categories"`
ReporterId int `json:"reporterId"` ReporterId int `json:"reporterId"`
ReporterCountryCode string `json:"reporterCountryCode"` ReporterCountryCode string `json:"reporterCountryCode"`
ReporterCountryName string `json:"reporterCountryName"` ReporterCountryName string `json:"reporterCountryName"`
} }
// Reports returns reports for a certain IP
func (c *Client) Reports(ctx context.Context, ip net.IP, opts *ReportsOptions) (*ReportsResult, error) { func (c *Client) Reports(ctx context.Context, ip net.IP, opts *ReportsOptions) (*ReportsResult, error) {
var endpoint = "reports"
ipAddress := html.EscapeString(ip.String()) ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{ parameters := map[string]string{
"ipAddress": ipAddress, "ipAddress": ipAddress,
@ -278,7 +322,7 @@ func (c *Client) Reports(ctx context.Context, ip net.IP, opts *ReportsOptions) (
} }
} }
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil) req, err := c.newRequest(http.MethodGet, endpointReports, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -332,12 +376,11 @@ func handleBlacklistOptions(opts *BlacklistOptions) map[string]string {
return parameters return parameters
} }
// Blacklist will generate a blacklist based on the Options
func (c *Client) Blacklist(ctx context.Context, opts *BlacklistOptions) (*BlacklistResult, error) { func (c *Client) Blacklist(ctx context.Context, opts *BlacklistOptions) (*BlacklistResult, error) {
var endpoint = "blacklist"
parameters := handleBlacklistOptions(opts) parameters := handleBlacklistOptions(opts)
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil) req, err := c.newRequest(http.MethodGet, endpointBlacklist, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -350,12 +393,11 @@ func (c *Client) Blacklist(ctx context.Context, opts *BlacklistOptions) (*Blackl
return &result, nil return &result, nil
} }
// BlacklistPlain same as Blacklist but returns a newline separated string
func (c *Client) BlacklistPlain(ctx context.Context, opts *BlacklistOptions) (io.Reader, error) { func (c *Client) BlacklistPlain(ctx context.Context, opts *BlacklistOptions) (io.Reader, error) {
var endpoint = "blacklist"
parameters := handleBlacklistOptions(opts) parameters := handleBlacklistOptions(opts)
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil) req, err := c.newRequest(http.MethodGet, endpointBlacklist, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -374,8 +416,9 @@ func (c *Client) BlacklistPlain(ctx context.Context, opts *BlacklistOptions) (io
return &result, nil return &result, nil
} }
// ReportOptions at least Categories is required
type ReportOptions struct { type ReportOptions struct {
Categories []int `json:"categories"` Categories []ReportCategory `json:"categories"`
Comment string `json:"comment,omitempty"` Comment string `json:"comment,omitempty"`
Time *time.Time `json:"timestamp,omitempty"` Time *time.Time `json:"timestamp,omitempty"`
} }
@ -387,9 +430,8 @@ type ReportResult struct {
} `json:"data"` } `json:"data"`
} }
// Report sends a report for an IP with additional Information
func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*ReportResult, error) { func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*ReportResult, error) {
var endpoint = "report"
ipAddress := html.EscapeString(ip.String()) ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{ parameters := map[string]string{
"ip": ipAddress, "ip": ipAddress,
@ -399,7 +441,7 @@ func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*R
if opts.Categories != nil { if opts.Categories != nil {
categories := make([]string, len(opts.Categories)) categories := make([]string, len(opts.Categories))
for i, category := range opts.Categories { for i, category := range opts.Categories {
categories[i] = strconv.Itoa(category) categories[i] = strconv.Itoa(int(category))
} }
parameters["categories"] = strings.Join(categories, ",") parameters["categories"] = strings.Join(categories, ",")
} }
@ -411,7 +453,7 @@ func (c *Client) Report(ctx context.Context, ip net.IP, opts *ReportOptions) (*R
} }
} }
req, err := c.newRequest(http.MethodPost, endpoint, parameters, nil) req, err := c.newRequest(http.MethodPost, endpointReport, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -445,13 +487,15 @@ type CheckBlockResult struct {
} `json:"data"` } `json:"data"`
} }
// CheckBlock test an IPNet of up to /16 and returns all addresses with reports, it is recommended to check /24 networks
func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckBlockOptions) (*CheckBlockResult, error) { func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckBlockOptions) (*CheckBlockResult, error) {
var endpoint = "check-block"
network := html.EscapeString(ipnNet.String()) network := html.EscapeString(ipnNet.String())
parameters := map[string]string{ parameters := map[string]string{
"network": network, "network": network,
} }
if cidr, _ := ipnNet.Mask.Size(); cidr < 16 {
return nil, fmt.Errorf("CIDR must be less than or equal to 16")
}
if opts != nil { if opts != nil {
if opts.MaxAgeInDays > 0 && opts.MaxAgeInDays <= 365 { if opts.MaxAgeInDays > 0 && opts.MaxAgeInDays <= 365 {
@ -459,7 +503,7 @@ func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckB
} }
} }
req, err := c.newRequest(http.MethodGet, endpoint, parameters, nil) req, err := c.newRequest(http.MethodGet, endpointCheckBlock, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -472,9 +516,35 @@ func (c *Client) CheckBlock(ctx context.Context, ipnNet *net.IPNet, opts *CheckB
return &result, nil return &result, nil
} }
// CheckBlockCached same as CheckBlock but uses the client cache and the ipnet as a key (cache has to be added to client for this to work)
func (c *Client) CheckBlockCached(ctx context.Context, ipNet *net.IPNet, opts *CheckBlockOptions) (*CheckBlockResult, error) {
if c.cache == nil {
return nil, errors.New("cache is missing")
}
key := fmt.Sprintf("%s:%s", endpointCheck, ipNet.String())
if r, err := c.cache.Get(key); err == nil {
result := CheckBlockResult{}
if jsonErr := json.NewDecoder(bytes.NewReader(r)).Decode(&result); jsonErr == nil {
return &result, nil
}
}
if result, err := c.CheckBlock(ctx, ipNet, opts); err == nil {
resultEncoded := bytes.Buffer{}
if jsonErr := json.NewEncoder(&resultEncoded).Encode(result); jsonErr != nil {
return nil, jsonErr
}
if re := resultEncoded.Bytes(); re != nil {
c.cache.Set(key, re)
}
return result, nil
} else {
return nil, err
}
}
type BulkReportData struct { type BulkReportData struct {
IpAddress string `csv:"IP"` IpAddress string `csv:"IP"`
Categories []int `csv:"Categories"` Categories []ReportCategory `csv:"Categories"`
ReportDate time.Time `csv:"ReportDate"` ReportDate time.Time `csv:"ReportDate"`
Comment string `csv:"Comment"` Comment string `csv:"Comment"`
} }
@ -510,7 +580,7 @@ func (b *BulkReportDatas) toSlice() [][]string {
for _, data := range *b { for _, data := range *b {
categories := make([]string, len(data.Categories)) categories := make([]string, len(data.Categories))
for i, category := range data.Categories { for i, category := range data.Categories {
categories[i] = strconv.Itoa(category) categories[i] = strconv.Itoa(int(category))
} }
slice = append(slice, []string{ slice = append(slice, []string{
data.IpAddress, data.IpAddress,
@ -578,9 +648,8 @@ type BulkReportResult struct {
} `json:"data"` } `json:"data"`
} }
// BulkReport takes a list of reports the success for ich reported entry
func (c *Client) BulkReport(ctx context.Context, data *BulkReportDatas) (*BulkReportResult, error) { func (c *Client) BulkReport(ctx context.Context, data *BulkReportDatas) (*BulkReportResult, error) {
var endpoint = "bulk-report"
if data == nil || len(*data) == 0 { if data == nil || len(*data) == 0 {
return nil, errors.New("bulk report: no data") return nil, errors.New("bulk report: no data")
} }
@ -593,7 +662,7 @@ func (c *Client) BulkReport(ctx context.Context, data *BulkReportDatas) (*BulkRe
} }
requestBody, contentType := toMultipartCsv(csvData) requestBody, contentType := toMultipartCsv(csvData)
req, err := c.newRequest(http.MethodPost, endpoint, nil, requestBody) req, err := c.newRequest(http.MethodPost, endpointBulkReport, nil, requestBody)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -613,14 +682,14 @@ type ClearAddressData struct {
} `json:"data"` } `json:"data"`
} }
// ClearAddress allow one to delete all reports made from your account for a certain address
func (c *Client) ClearAddress(ctx context.Context, ip net.IP) (*ClearAddressData, error) { func (c *Client) ClearAddress(ctx context.Context, ip net.IP) (*ClearAddressData, error) {
var endpoint = "clear-address"
ipAddress := html.EscapeString(ip.String()) ipAddress := html.EscapeString(ip.String())
parameters := map[string]string{ parameters := map[string]string{
"ipAddress": ipAddress, "ipAddress": ipAddress,
} }
req, err := c.newRequest(http.MethodDelete, endpoint, parameters, nil) req, err := c.newRequest(http.MethodDelete, endpointClearAddress, parameters, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }