From 1f965f44bd705b4513322f8c8d760088ef9099b9 Mon Sep 17 00:00:00 2001 From: luizm Date: Sun, 26 Apr 2020 20:08:54 -0300 Subject: [PATCH] add dns and rds support --- README.md | 58 ++++++++++++++--- cmd/cmd.go | 19 +++++- pkg/whois.go | 177 +++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 209 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 7f44a8b..9638804 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ **Still work in progress** -If you have a lot of accounts, this is a simple way to check which resource on AWS are using a specific public or private IP. +If you have a lot of accounts and VPCs, this is a simple way to check where or which resource on AWS are using a specific public or private IP. -## Install +## Install -```sh +```sh $ brew install luizm/tap/aws-whois ``` @@ -14,7 +14,11 @@ Or download the binary from [github releases](https://github.com/luizm/aws-whois ## Usage -`$ aws-whois --profile example --ip 172.20.X.X` +Basically use the `flag` --ip or `--dns`. + +Examples: + +`$ aws-whois --profile example --ip 54.X.X.X` ```log { @@ -29,30 +33,64 @@ Or download the binary from [github releases](https://github.com/luizm/aws-whois } ``` -`$ aws-whois --profile example --profile example2 --ip 54.X.X.X` +When use the flag `--dns` the address will be resolved before find out the resource. + +`$ aws-whois --profile example --profile example2 --dns example.mydomain.local` ```log { "Profile": "example", - "Message": "ip 54.X.X.X not found" + "Message": "ip 172.20.X.X not found" } { "Profile": "example2", "ENI": { - "OwnerId": "xxxxxx", + "Status": "in-use", + "VPCId": "vpc-xxxxx", + "VPCName": "example", "Description": "Primary network interface", "InterfaceType": "interface", "AvailabilityZone": "us-east-1a", "EC2Associated": { - "VpcId": "vpc-xxxxxx", - "InstanceId": "i-xxxxxx", + "InstanceId": "i-xxxxx", + "InstanceState": "running", + "LaunchTime": "2020-04-20T19:59:40Z", "InstanceType": "t3.micro", "Tags": [ { "Key": "Name", - "Value": "example.local" + "Value": "example.mydomain.local" } ] + }, + "RDSAssociated": null + } +} +``` + +`$ aws-whois --profile example --profile example3 --ip 10.100.X.X` + +```log +{ + "Profile": "example", + "Message": "ip 10.100.X.X not found" +} +{ + "Profile": "example3", + "ENI": { + "Status": "in-use", + "VPCId": "vpc-xxxxx", + "VPCName": "production", + "Description": "RDSNetworkInterface", + "InterfaceType": "interface", + "AvailabilityZone": "us-east-1c", + "EC2Associated": null, + "RDSAssociated": { + "Identifier": "rds-example", + "Engine": "postgres", + "DBInstanceClass": "db.m5.large", + "Endpoint": "rds-example.xxxxx.us-east-1.rds.amazonaws.com", + "Status": "available" } } } diff --git a/cmd/cmd.go b/cmd/cmd.go index e7cc2bb..18be358 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -28,13 +28,26 @@ func Execute() { &cli.StringFlag{ Name: "ip", Aliases: []string{"i"}, - Usage: "The ip address to find", - Required: true, + Usage: "The ip address to find the resource associated", + Required: false, + }, + &cli.StringFlag{ + Name: "dns", + Aliases: []string{"d"}, + Usage: "The dns address to find the resource associated, if return more than 1 ip, will be used the first", + Required: false, }, }, Action: func(c *cli.Context) error { + var ip string + ip = c.String("ip") + + if c.String("dns") != "" { + ips, _ := whois.ResolvDNS(c.String("dns")) + ip = ips[0] + } for _, p := range c.StringSlice("profile") { - result, err := whois.FindIP(p, c.String("region"), c.String("ip")) + result, err := whois.FindIP(p, c.String("region"), ip) if err != nil { return err } diff --git a/pkg/whois.go b/pkg/whois.go index b53f4c6..f9de1a5 100644 --- a/pkg/whois.go +++ b/pkg/whois.go @@ -5,10 +5,12 @@ import ( "errors" "fmt" "net" + "time" "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/ec2" + "github.com/aws/aws-sdk-go/service/rds" ) type Result struct { @@ -22,18 +24,70 @@ type ResultNotFond struct { } type DefaultOutput struct { - OwnerId *string `json:"OwnerId"` + Status *string `json:"Status"` + VPCId *string `json:"VPCId"` + VPCName *string `json:"VPCName"` Description *string `json:"Description"` InterfaceType *string `json:"InterfaceType"` AvailabilityZone *string `json:"AvailabilityZone"` EC2Associated *EC2Output + RDSAssociated *RDSOutput +} + +type RDSEndpoint struct { + Identifier *string + Endpoint *string } type EC2Output struct { - VpcId *string `json:"VpcId"` - InstanceId *string `json:"InstanceId"` - InstanceType *string `json:"InstanceType"` - Tags []*ec2.Tag `json:"Tags"` + InstanceId *string `json:"InstanceId"` + InstanceState *string `json:"InstanceState"` + LaunchTime *time.Time `json:"LaunchTime"` + InstanceType *string `json:"InstanceType"` + Tags []*ec2.Tag `json:"Tags"` +} + +type RDSOutput struct { + Identifier *string `json:"Identifier"` + Engine *string `json:"Engine"` + DBInstanceClass *string `json:"DBInstanceClass"` + Endpoint *string `json:"Endpoint"` + Status *string `json:"Status"` +} + +func newSession(profile, region string) (*session.Session, error) { + sess, err := session.NewSessionWithOptions(session.Options{ + Profile: profile, + + Config: aws.Config{ + Region: aws.String(region), + }, + + // Force enable Shared Config support + SharedConfigState: session.SharedConfigEnable, + }) + return sess, err +} + +func getVPCName(sess *session.Session, v *string) (*string, error) { + svc := ec2.New(sess) + var n *string + + r, err := svc.DescribeVpcs(&ec2.DescribeVpcsInput{ + VpcIds: []*string{ + aws.String(*v), + }, + }) + if err != nil { + return nil, err + } + for _, t := range r.Vpcs[0].Tags { + if *t.Key == "Name" { + n = t.Value + break + } + } + return n, nil } func isPrivateIP(ip string) (bool, error) { @@ -50,8 +104,46 @@ func isPrivateIP(ip string) (bool, error) { return private, nil } -func getInstanceInfo(sess *session.Session, i *string) (*EC2Output, error) { +func getRDSEndpoints(sess *session.Session) ([]RDSEndpoint, error) { + var e []RDSEndpoint + svc := rds.New(sess) + + result, err := svc.DescribeDBInstances(&rds.DescribeDBInstancesInput{}) + if err != nil { + if err != nil { + return nil, err + } + } + for _, r := range result.DBInstances { + e = append(e, RDSEndpoint{ + Identifier: r.DBInstanceIdentifier, + Endpoint: r.Endpoint.Address, + }) + } + return e, nil +} + +func getRDSInfo(sess *session.Session, r *string) (*RDSOutput, error) { + svc := rds.New(sess) + + result, err := svc.DescribeDBInstances(&rds.DescribeDBInstancesInput{ + DBInstanceIdentifier: r, + }) + if err != nil { + if err != nil { + return nil, err + } + } + return &RDSOutput{ + Identifier: result.DBInstances[0].DBInstanceIdentifier, + Engine: result.DBInstances[0].Engine, + DBInstanceClass: result.DBInstances[0].DBInstanceClass, + Endpoint: result.DBInstances[0].Endpoint.Address, + Status: result.DBInstances[0].DBInstanceStatus, + }, nil +} +func getEC2Info(sess *session.Session, i *string) (*EC2Output, error) { svc := ec2.New(sess) result, err := svc.DescribeInstances(&ec2.DescribeInstancesInput{ @@ -60,30 +152,16 @@ func getInstanceInfo(sess *session.Session, i *string) (*EC2Output, error) { if err != nil { return nil, err } - return &EC2Output{ - VpcId: result.Reservations[0].Instances[0].VpcId, - InstanceId: result.Reservations[0].Instances[0].InstanceId, - InstanceType: result.Reservations[0].Instances[0].InstanceType, - Tags: result.Reservations[0].Instances[0].Tags, + InstanceId: result.Reservations[0].Instances[0].InstanceId, + InstanceType: result.Reservations[0].Instances[0].InstanceType, + InstanceState: result.Reservations[0].Instances[0].State.Name, + LaunchTime: result.Reservations[0].Instances[0].LaunchTime, + Tags: result.Reservations[0].Instances[0].Tags, }, nil } -func newSession(profile, region string) (*session.Session, error) { - sess, err := session.NewSessionWithOptions(session.Options{ - Profile: profile, - - Config: aws.Config{ - Region: aws.String(region), - }, - - // Force enable Shared Config support - SharedConfigState: session.SharedConfigEnable, - }) - return sess, err -} - -func getInterfaceInfo(sess *session.Session, ip string) (*ec2.DescribeNetworkInterfacesOutput, error) { +func getENIInfo(sess *session.Session, ip string) (*ec2.DescribeNetworkInterfacesOutput, error) { svc := ec2.New(sess) filter := "association.public-ip" @@ -114,15 +192,29 @@ func getInterfaceInfo(sess *session.Session, ip string) (*ec2.DescribeNetworkInt return result, nil } +func ResolvDNS(dns string) ([]string, error) { + var ips []string + i, err := net.LookupIP(dns) + if err != nil { + return nil, err + } + + for _, i := range i { + ips = append(ips, i.String()) + } + return ips, nil +} + func FindIP(profile, region, ip string) ([]byte, error) { var e *EC2Output + var r *RDSOutput sess, err := newSession(profile, region) if err != nil { return nil, err } - result, err := getInterfaceInfo(sess, ip) + result, err := getENIInfo(sess, ip) if err != nil { return nil, err } @@ -138,22 +230,43 @@ func FindIP(profile, region, ip string) ([]byte, error) { return b, nil } + VPCName, err := getVPCName(sess, result.NetworkInterfaces[0].VpcId) + if err != nil { + return nil, err + } + if *result.NetworkInterfaces[0].Description == "RDSNetworkInterface" { + endpoints, err := getRDSEndpoints(sess) + if err != nil { + return nil, err + } + + for _, e := range endpoints { + i, _ := ResolvDNS(string(*e.Endpoint)) + if ip == string(i[0]) { + r, _ = getRDSInfo(sess, e.Identifier) + break + } + } + } + instanceID := result.NetworkInterfaces[0].Attachment.InstanceId if instanceID != nil { - e, _ = getInstanceInfo(sess, instanceID) + e, _ = getEC2Info(sess, instanceID) } - - r := Result{ + o := Result{ Profile: profile, ENI: &DefaultOutput{ - OwnerId: result.NetworkInterfaces[0].OwnerId, + Status: result.NetworkInterfaces[0].Status, + VPCId: result.NetworkInterfaces[0].VpcId, + VPCName: VPCName, Description: result.NetworkInterfaces[0].Description, InterfaceType: result.NetworkInterfaces[0].InterfaceType, AvailabilityZone: result.NetworkInterfaces[0].AvailabilityZone, EC2Associated: e, + RDSAssociated: r, }, } - b, err := json.MarshalIndent(r, "", " ") + b, err := json.MarshalIndent(o, "", " ") if err != nil { return nil, err }