├── .gitignore ├── go.mod ├── main.go ├── secrets.go ├── LICENSE ├── acls.go ├── tags.go ├── README.md └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | hello 2 | function.zip 3 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DentonGentry/tailscale-aws-host-acl-updater 2 | 3 | go 1.19 4 | 5 | require ( 6 | github.com/aws/aws-lambda-go v1.34.1 7 | github.com/aws/aws-sdk-go-v2 v1.16.11 8 | github.com/aws/aws-sdk-go-v2/config v1.16.1 9 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.52.1 10 | github.com/aws/aws-sdk-go-v2/service/rds v1.23.6 11 | github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f 12 | ) 13 | 14 | require ( 15 | github.com/aws/aws-sdk-go v1.44.81 // indirect 16 | github.com/aws/aws-sdk-go-v2/credentials v1.12.13 // indirect 17 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 // indirect 18 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 // indirect 19 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 // indirect 20 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 // indirect 21 | github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.12 // indirect 22 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 // indirect 23 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.16 // indirect 24 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 // indirect 25 | github.com/aws/smithy-go v1.12.1 // indirect 26 | github.com/jmespath/go-jmespath v0.4.0 // indirect 27 | github.com/stretchr/testify v1.7.4 // indirect 28 | ) 29 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "os" 7 | 8 | "github.com/aws/aws-lambda-go/events" 9 | "github.com/aws/aws-lambda-go/lambda" 10 | "github.com/aws/aws-sdk-go-v2/config" 11 | "github.com/aws/aws-sdk-go-v2/service/ec2" 12 | elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" 13 | "github.com/aws/aws-sdk-go-v2/service/rds" 14 | ) 15 | 16 | var ( 17 | tsApiKey string 18 | tailnet string 19 | tsControlServer string 20 | ) 21 | 22 | func HandleRequest(ctx context.Context, event *events.CloudWatchEvent) { 23 | hosts := tagsHandler(ctx, event) 24 | if len(hosts) > 0 { 25 | updateHosts(ctx, hosts) 26 | } 27 | } 28 | 29 | func main() { 30 | cfg, err := config.LoadDefaultConfig(context.Background()) 31 | if err != nil { 32 | log.Fatalf("unable to load SDK config, %v", err) 33 | } 34 | ec2Client = ec2.NewFromConfig(cfg) 35 | rdsClient = rds.NewFromConfig(cfg) 36 | elbClient = elb.NewFromConfig(cfg) 37 | 38 | tailnet = os.Getenv("TAILSCALE_TAILNET") 39 | tsControlServer = os.Getenv("TAILSCALE_CONTROL_SERVER") 40 | if tsControlServer == "" { 41 | tsControlServer = "https://login.tailscale.com" 42 | } 43 | 44 | tsApiKey, err = getApiKey() 45 | if err != nil { 46 | log.Fatalf("getApiKey failed: %v", err) 47 | } 48 | 49 | lambda.Start(HandleRequest) 50 | } 51 | -------------------------------------------------------------------------------- /secrets.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "errors" 7 | "os" 8 | 9 | "github.com/aws/aws-sdk-go/aws" 10 | "github.com/aws/aws-sdk-go/aws/session" 11 | "github.com/aws/aws-sdk-go/service/secretsmanager" 12 | ) 13 | 14 | func getApiKey() (string, error) { 15 | region := os.Getenv("AWS_REGION") 16 | secretId := os.Getenv("SECRET_NAME") 17 | if secretId == "" { 18 | secretId = "ts-acl-hostname-updater" 19 | } 20 | 21 | input := &secretsmanager.GetSecretValueInput{ 22 | SecretId: aws.String(secretId), 23 | VersionStage: aws.String("AWSCURRENT"), 24 | } 25 | 26 | secretsManager := secretsmanager.New( 27 | session.New(), 28 | aws.NewConfig().WithRegion(region), 29 | ) 30 | 31 | result, err := secretsManager.GetSecretValue(input) 32 | if err != nil { 33 | return "", err 34 | } 35 | 36 | secret := []byte{} 37 | if result.SecretString != nil { 38 | secret = []byte(*result.SecretString) 39 | } else { 40 | secret, err = base64.StdEncoding.DecodeString(string(result.SecretBinary)) 41 | if err != nil { 42 | return "", err 43 | } 44 | } 45 | 46 | values := make(map[string]string) 47 | err = json.Unmarshal(secret, &values) 48 | if err != nil { 49 | return "", err 50 | } 51 | 52 | apiKey, ok := values["API_KEY"] 53 | if !ok { 54 | return "", errors.New("No API_KEY in SecretsManager") 55 | } 56 | 57 | return apiKey, nil 58 | } 59 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2020 Tailscale & AUTHORS. 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | 3. Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /acls.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "errors" 7 | "fmt" 8 | "io/ioutil" 9 | "log" 10 | "net/http" 11 | 12 | "github.com/tailscale/hujson" 13 | ) 14 | 15 | var aclUpdateRetry = errors.New("If-Match condition failed") 16 | 17 | func getAcls() (acls hujson.Value, etag string, err error) { 18 | req, err := http.NewRequest("GET", tsControlServer+"/api/v2/tailnet/"+tailnet+"/acl", nil) 19 | if err != nil { 20 | return hujson.Value{}, "", err 21 | } 22 | req.SetBasicAuth(tsApiKey, "") 23 | 24 | resp, err := http.DefaultClient.Do(req) 25 | if err != nil { 26 | return hujson.Value{}, "", err 27 | } 28 | 29 | if resp.StatusCode != http.StatusOK { 30 | return hujson.Value{}, "", nil 31 | } 32 | 33 | body, err := ioutil.ReadAll(resp.Body) 34 | if err != nil { 35 | return hujson.Value{}, "", err 36 | } 37 | 38 | acls, err = hujson.Parse(body) 39 | if err != nil { 40 | return hujson.Value{}, "", err 41 | } 42 | 43 | return acls, resp.Header.Get("ETag"), nil 44 | } 45 | 46 | func putAcls(acls hujson.Value, etag string) error { 47 | url := tsControlServer + "/api/v2/tailnet/" + tailnet + "/acl" 48 | req, err := http.NewRequest("POST", url, bytes.NewBufferString(acls.String())) 49 | if err != nil { 50 | return err 51 | } 52 | req.SetBasicAuth(tsApiKey, "") 53 | req.Header.Set("Content-Type", "application/hujson") 54 | req.Header.Set("If-Match", etag) 55 | 56 | resp, err := http.DefaultClient.Do(req) 57 | if err != nil { 58 | return err 59 | } 60 | 61 | if resp.StatusCode == http.StatusPreconditionFailed { 62 | return aclUpdateRetry 63 | } else if resp.StatusCode != http.StatusOK { 64 | return errors.New(fmt.Sprintf("HTTP POST failed: %d", resp.StatusCode)) 65 | } 66 | 67 | return nil 68 | } 69 | 70 | func updateHosts(ctx context.Context, update map[string]string) { 71 | retry := true 72 | for retry { 73 | retry = false 74 | changed := false 75 | acls, etag, err := getAcls() 76 | if err != nil { 77 | log.Printf("getAcls failed: %v", err) 78 | break 79 | } 80 | if etag == "" { 81 | log.Printf("getAcls returned empty") 82 | break 83 | } 84 | 85 | for key, value := range update { 86 | patch := `[{ "op": "replace", "path": "/Hosts/` + key + `", "value": "` + value + `" }]` 87 | err = acls.Patch([]byte(patch)) 88 | if err == nil { 89 | changed = true 90 | } 91 | } 92 | 93 | if changed { 94 | err = putAcls(acls, etag) 95 | if err != nil { 96 | if errors.Is(err, aclUpdateRetry) { 97 | // If-Match failed, collision in updating ACLs 98 | retry = true 99 | } else { 100 | log.Printf("putAcls failed: %v", err) 101 | } 102 | } 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /tags.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "log" 6 | "net" 7 | "strings" 8 | 9 | "github.com/aws/aws-lambda-go/events" 10 | "github.com/aws/aws-sdk-go-v2/aws/arn" 11 | "github.com/aws/aws-sdk-go-v2/service/ec2" 12 | elb "github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2" 13 | "github.com/aws/aws-sdk-go-v2/service/rds" 14 | ) 15 | 16 | var ( 17 | ec2Client *ec2.Client 18 | elbClient *elb.Client 19 | rdsClient *rds.Client 20 | ) 21 | 22 | func getEc2VpcAddress(ctx context.Context, resource string) (hostname, ip string) { 23 | id := strings.TrimPrefix(resource, "instance/") 24 | params := &ec2.DescribeInstancesInput{ 25 | InstanceIds: []string{id}, 26 | } 27 | output, err := ec2Client.DescribeInstances(ctx, params) 28 | if err != nil { 29 | log.Printf("ec2.DescribeInstances(%s) failed: %v", id, err) 30 | return "", "" 31 | } 32 | if len(output.Reservations) != 1 || len(output.Reservations[0].Instances) != 1 { 33 | return "", "" 34 | } 35 | 36 | instance := output.Reservations[0].Instances[0] 37 | for _, tag := range instance.Tags { 38 | if *tag.Key == "ts-hostname" { 39 | hostname = *tag.Value 40 | break 41 | } 42 | } 43 | if hostname != "" { 44 | ip = *instance.PrivateIpAddress 45 | } 46 | 47 | return 48 | } 49 | 50 | func getRdsVpcAddress(ctx context.Context, arn string) (hostname, ip string) { 51 | tagsParams := &rds.ListTagsForResourceInput{ 52 | ResourceName: &arn, 53 | } 54 | tagsOutput, err := rdsClient.ListTagsForResource(ctx, tagsParams) 55 | if err != nil { 56 | log.Printf("rds.ListTagsForResource(%s) failed: %v", arn, err) 57 | return "", "" 58 | } 59 | instanceParams := &rds.DescribeDBInstancesInput{ 60 | DBInstanceIdentifier: &arn, 61 | } 62 | instanceOutput, err := rdsClient.DescribeDBInstances(ctx, instanceParams) 63 | if err != nil { 64 | log.Printf("rds.DescribeDBInstances(%s) failed: %v", arn, err) 65 | return "", "" 66 | } 67 | if len(instanceOutput.DBInstances) != 1 { 68 | return "", "" 69 | } 70 | 71 | rdsHostname := "" 72 | for _, tag := range tagsOutput.TagList { 73 | if *tag.Key == "ts-hostname" { 74 | rdsHostname = *tag.Value 75 | break 76 | } 77 | } 78 | if rdsHostname == "" { 79 | return "", "" 80 | } 81 | 82 | dnsName := instanceOutput.DBInstances[0].Endpoint.Address 83 | ips, err := net.LookupIP(*dnsName) 84 | if err != nil { 85 | log.Printf("net.LookupIP(%s) failed: %v", dnsName, err) 86 | return "", "" 87 | } 88 | if len(ips) < 1 { 89 | log.Printf("net.LookupIP(%s): no results", dnsName) 90 | return "", "" 91 | } 92 | 93 | ip = ips[0].String() 94 | hostname = rdsHostname 95 | return 96 | } 97 | 98 | func getElbVpcAddress(ctx context.Context, arn string) (hostname, ip string) { 99 | tagsParams := &elb.DescribeTagsInput{ 100 | ResourceArns: []string{arn}, 101 | } 102 | tagsOutput, err := elbClient.DescribeTags(ctx, tagsParams) 103 | if err != nil { 104 | log.Printf("elb.DescribeTags(%s) failed: %v", arn, err) 105 | return "", "" 106 | } 107 | 108 | elbParams := &elb.DescribeLoadBalancersInput{ 109 | LoadBalancerArns: []string{arn}, 110 | } 111 | instanceOutput, err := elbClient.DescribeLoadBalancers(ctx, elbParams) 112 | if err != nil { 113 | log.Printf("elb.DescribeLoadBalancers(%s) failed: %v", arn, err) 114 | return "", "" 115 | } 116 | if len(instanceOutput.LoadBalancers) != 1 { 117 | return "", "" 118 | } 119 | 120 | elbHostname := "" 121 | for _, desc := range tagsOutput.TagDescriptions { 122 | for _, tag := range desc.Tags { 123 | if *tag.Key == "ts-hostname" { 124 | elbHostname = *tag.Value 125 | break 126 | } 127 | } 128 | } 129 | if elbHostname == "" { 130 | return "", "" 131 | } 132 | 133 | dnsName := instanceOutput.LoadBalancers[0].DNSName 134 | ips, err := net.LookupIP(*dnsName) 135 | if err != nil { 136 | log.Printf("net.LookupIP(%s) failed: %v", *dnsName, err) 137 | return "", "" 138 | } 139 | if len(ips) < 1 { 140 | log.Printf("net.LookupIP(%s): no results", dnsName) 141 | return "", "" 142 | } 143 | 144 | ip = ips[0].String() 145 | hostname = elbHostname 146 | return 147 | } 148 | 149 | func tagsHandler(ctx context.Context, event *events.CloudWatchEvent) (hosts map[string]string) { 150 | hosts = make(map[string]string) 151 | for _, resource := range event.Resources { 152 | a, err := arn.Parse(resource) 153 | if err != nil { 154 | log.Printf("arn.Parse(%s) failed: %v", resource, err) 155 | continue 156 | } 157 | switch a.Service { 158 | case "ec2": 159 | hostname, ip := getEc2VpcAddress(ctx, a.Resource) 160 | if hostname != "" && ip != "" { 161 | hosts[hostname] = ip 162 | } 163 | case "rds": 164 | hostname, ip := getRdsVpcAddress(ctx, resource) // RDS APIs use full ARN 165 | if hostname != "" && ip != "" { 166 | hosts[hostname] = ip 167 | } 168 | case "elasticloadbalancing": 169 | hostname, ip := getElbVpcAddress(ctx, resource) 170 | if hostname != "" && ip != "" { 171 | hosts[hostname] = ip 172 | } 173 | } 174 | } 175 | return 176 | } 177 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tailscale AWS hostname monitor 2 | 3 | https://tailscale.com 4 | 5 | ## Overview 6 | 7 | This repository contains a set of AWS Lambda functions intended to: 8 | - receive notifications of changes in AWS tags across a number of 9 | AWS services 10 | - check for an AWS tag named "ts-hostname" 11 | - automatically populate the `Hosts` section of a Tailscale ACL 12 | Policy file to update the IP address of that hostname 13 | 14 | This is intended to be used in conjunction with a Tailscale 15 | [subnet router](https://tailscale.com/kb/1019/subnets/) offering 16 | a Tailscale route to a private AWS VPC. ACLs can be defined for 17 | the private 172.16.x.y VPC IP addresses, relying on this Lambda 18 | function to update the IP address if the AWS instance is replaced. 19 | 20 | ## Setup 21 | 22 | ### Step 1: Lambda function 23 | Create a Lambda function in the AWS Console in the AWS region where 24 | the EC2/RDS/etc resources used with Tailscale are located. 25 | 26 | CloudWatch events can only be sent to a Lambda function in the same 27 | region. A Lambda will be needed in each region where there are 28 | resources to be tracked. 29 | 30 | 31 | ### Step 2: Permissions 32 | In Lambda > Configuration > Permissions, the ARN of the role created for the 33 | Lambda function will be shown. It needs to be given several permissions. 34 | 35 | a. It needs to be able to read EC2 Instance Details. Attach the 36 | `AmazonEC2ReadOnlyAccess` policy to the role. 37 | 38 | b. It needs to be able to get RDS instance details and tags. Create 39 | an inline policy of: 40 | ``` 41 | { 42 | "Version": "2012-10-17", 43 | "Statement": [ 44 | { 45 | "Sid": "AllowRDSTagsRead", 46 | "Effect": "Allow", 47 | "Action": [ 48 | "rds:DescribeDBInstances", 49 | "rds:ListTagsForResource" 50 | ], 51 | "Resource": "*" 52 | } 53 | ] 54 | } 55 | ``` 56 | 57 | ### Step 3: Configuration 58 | In Lambda > Configuration > Environment variables, create the following variable: 59 | - `TAILSCALE_TAILNET`: the name of the tailnet, such as `example.com` or `octocat.github` 60 | 61 | 62 | In the AWS Secrets Manager create a secret containing a Key/Value pair: 63 | - Key: `API_KEY` 64 | - Value: the `tskey-...` of a [Tailscale API key](https://tailscale.com/kb/1101/api/). 65 | 66 | In the Replicate Secret section you can replicate the same secret across all regions 67 | where you intend to run this Lambda function. All of them can use the same API Key. 68 | 69 | We suggest naming the secret `ts-acl-hostname-updater`, but you may choose 70 | whatever naming convention you prefer by setting the `SECRET_NAME` environment variable. 71 | 72 | In the role created for the Lambda function (in Lambda > Configuration > Permissions) add 73 | another inline policy to access the secret: 74 | ``` 75 | { 76 | "Version": "2012-10-17", 77 | "Statement": [ 78 | { 79 | "Effect": "Allow", 80 | "Action": [ 81 | "secretsmanager:GetSecretValue" 82 | ], 83 | "Resource": [ 84 | "ARN_OF_SECRET" 85 | ] 86 | } 87 | ] 88 | } 89 | ``` 90 | 91 | ### Step 4: EventBridge 92 | Create an EventBridge event with pattern: 93 | ``` 94 | { 95 | "source": ["aws.tag"], 96 | "detail-type": ["Tag Change on Resource"], 97 | "detail": { 98 | "service": ["ec2", "rds", "elasticloadbalancing"] 99 | } 100 | } 101 | ``` 102 | This will run the Lambda function whenever tags are changed on an EC2 or RDS instance. 103 | 104 | Create a second EventBridge rule with the pattern: 105 | ``` 106 | { 107 | "source": ["aws.rds"], 108 | "detail-type": ["RDS DB Instance Event"] 109 | } 110 | ``` 111 | This will run the Lambda function whenever the RDS instance reboots or otherwise 112 | changes its running state. 113 | 114 | Create a third EventBridge rule with the pattern: 115 | ``` 116 | { 117 | "source": ["aws.elasticloadbalancing"], 118 | "detail-type": ["AWS API Call via CloudTrail"], 119 | "detail": { 120 | "eventSource": ["elasticloadbalancing.amazonaws.com"], 121 | "eventName": ["CreateLoadBalancer"] 122 | } 123 | } 124 | ``` 125 | This will run the Lambda function whenever a new Load Balancer instance is created. 126 | Unfortunately AWS does not generate CloudWatch events for other Load Balancer state 127 | changes, we cannot automatically run the Lambda. 128 | 129 | 130 | ### Step 5: Add `ts-hostname` AWS Tags 131 | For any AWS resource for which you wish this Lambda function to keep the IP 132 | address up to date, two things need to be done: 133 | 1. Add an AWS Tag with key `ts-hostname` and value of the hostname which it is to maintain. 134 | This is an _AWS_ Tag, not a Tag in the Tailscale ACL file. 135 | 1. Add an initial entry for the hostname in the [Tailscale ACLs Hosts section](https://login.tailscale.com/admin/acls). 136 | This Lambda function will update a `Hosts` entry which is already present. It will not 137 | add a new Hosts entry. This initial entry can be any valid IP address like `1.2.3.4` 138 | 139 | ## Bugs 140 | 141 | Please file any issues about this function on 142 | [the issue tracker](https://github.com/tailscale/tailscale/issues). 143 | 144 | ## Contributing 145 | 146 | PRs welcome! But please file bugs. Commit messages should [reference 147 | bugs](https://docs.github.com/en/github/writing-on-github/autolinked-references-and-urls). 148 | 149 | We require [Developer Certificate of 150 | Origin](https://en.wikipedia.org/wiki/Developer_Certificate_of_Origin) 151 | `Signed-off-by` lines in commits. 152 | 153 | ## About Us 154 | 155 | [Tailscale](https://tailscale.com/) is primarily developed by the 156 | people at https://github.com/orgs/tailscale/people. For other contributors, 157 | see: 158 | 159 | * https://github.com/tailscale/tailscale/graphs/contributors 160 | * https://github.com/tailscale/tailscale-android/graphs/contributors 161 | 162 | ## Legal 163 | 164 | WireGuard is a registered trademark of Jason A. Donenfeld. 165 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/aws/aws-lambda-go v1.34.1 h1:M3a/uFYBjii+tDcOJ0wL/WyFi2550FHoECdPf27zvOs= 2 | github.com/aws/aws-lambda-go v1.34.1/go.mod h1:jwFe2KmMsHmffA1X2R09hH6lFzJQxzI8qK17ewzbQMM= 3 | github.com/aws/aws-sdk-go v1.44.81 h1:C8oBZ+a+ka0qk3Q24MohQIFq0tkbO8IAu5tfpAMKVWE= 4 | github.com/aws/aws-sdk-go v1.44.81/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= 5 | github.com/aws/aws-sdk-go-v2 v1.16.11 h1:xM1ZPSvty3xVmdxiGr7ay/wlqv+MWhH0rMlyLdbC0YQ= 6 | github.com/aws/aws-sdk-go-v2 v1.16.11/go.mod h1:WTACcleLz6VZTp7fak4EO5b9Q4foxbn+8PIz3PmyKlo= 7 | github.com/aws/aws-sdk-go-v2/config v1.16.1 h1:jasqFPOoNPXHOYGEEuvyT87ACiXhD3OkQckIm5uqi5I= 8 | github.com/aws/aws-sdk-go-v2/config v1.16.1/go.mod h1:4SKzBMiB8lV0fw2w7eDBo/LjQyHFITN4vUUuqpurFmI= 9 | github.com/aws/aws-sdk-go-v2/credentials v1.12.13 h1:cuPzIsjKAWBUAAk8ZUR2l02Sxafl9hiaMsc7tlnjwAY= 10 | github.com/aws/aws-sdk-go-v2/credentials v1.12.13/go.mod h1:9fDEemXizwXrxPU1MTzv69LP/9D8HVl5qHAQO9A9ikY= 11 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12 h1:wgJBHO58Pc1V1QAnzdVM3JK3WbE/6eUF0JxCZ+/izz0= 12 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.12/go.mod h1:aZ4vZnyUuxedC7eD4JyEHpGnCz+O2sHQEx3VvAwklSE= 13 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18 h1:OmiwoVyLKEqqD5GvB683dbSqxiOfvx4U2lDZhG2Esc4= 14 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.18/go.mod h1:348MLhzV1GSlZSMusdwQpXKbhD7X2gbI/TxwAPKkYZQ= 15 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12 h1:5mvQDtNWtI6H56+E4LUnLWEmATMB7oEh+Z9RurtIuC0= 16 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.12/go.mod h1:ckaCVTEdGAxO6KwTGzgskxR1xM+iJW4lxMyDFVda2Fc= 17 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19 h1:g5qq9sgtEzt2szMaDqQO6fqKe026T6dHTFJp5NsPzkQ= 18 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.19/go.mod h1:cVHo8KTuHjShb9V8/VjH3S/8+xPu16qx8fdGwmotJhE= 19 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.52.1 h1:A2hit+4GRYOdvs2aJxGhDrrRS17zSa66M+k1IqqgUic= 20 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.52.1/go.mod h1:YbPg6ou7dlvFTJMmbV3zhec+A22S1Ow+ZB6k6xUs9oY= 21 | github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.12 h1:jemAfH91rYzeDdNPDNdZHLSXxaXW5l1fcUT1+nRQ8cM= 22 | github.com/aws/aws-sdk-go-v2/service/elasticloadbalancingv2 v1.18.12/go.mod h1:X2UdAVE3dDmC83sWf9gXW3EL2mVjDCS4vRUctHz8GjM= 23 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12 h1:7iPTTX4SAI2U2VOogD7/gmHlsgnYSgoNHt7MSQXtG2M= 24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.12/go.mod h1:1TODGhheLWjpQWSuhYuAUWYTCKwEjx2iblIFKDHjeTc= 25 | github.com/aws/aws-sdk-go-v2/service/rds v1.23.6 h1:eifAkL7TQwDPq6Uq57oN04Xi9H+aHrm+jgJCmlpoSPQ= 26 | github.com/aws/aws-sdk-go-v2/service/rds v1.23.6/go.mod h1:0+TdWzMBupUemfH+AlJ55BSD/KNPKyIcf5X3++cJOTA= 27 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.16 h1:YK8L7TNlGwMWHYqLs+i6dlITpxqzq08FqQUy26nm+T8= 28 | github.com/aws/aws-sdk-go-v2/service/sso v1.11.16/go.mod h1:mS5xqLZc/6kc06IpXn5vRxdLaED+jEuaSRv5BxtnsiY= 29 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.13 h1:dl8T0PJlN92rvEGOEUiD0+YPYdPEaCZK0TqHukvSfII= 30 | github.com/aws/aws-sdk-go-v2/service/sts v1.16.13/go.mod h1:Ru3QVMLygVs/07UQ3YDur1AQZZp2tUNje8wfloFttC0= 31 | github.com/aws/smithy-go v1.12.1 h1:yQRC55aXN/y1W10HgwHle01DRuV9Dpf31iGkotjt3Ag= 32 | github.com/aws/smithy-go v1.12.1/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA= 33 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 34 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 35 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 36 | github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= 37 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 38 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 39 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 40 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 41 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 42 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 46 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 47 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 48 | github.com/stretchr/testify v1.7.4 h1:wZRexSlwd7ZXfKINDLsO4r7WBt3gTKONc6K/VesHvHM= 49 | github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 50 | github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f h1:n4r/sJ92cBSBHK8n9lR1XLFr0OiTVeGfN5TR+9LaN7E= 51 | github.com/tailscale/hujson v0.0.0-20220630195928-54599719472f/go.mod h1:DFSS3NAGHthKo1gTlmEcSBiZrRJXi28rLNd/1udP1c8= 52 | golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= 53 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 54 | golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 55 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 56 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 57 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 58 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 59 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 60 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 61 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 62 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 63 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 64 | --------------------------------------------------------------------------------