-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
cmd/connector-gen: add helper tool for wide app connector configurations
connector-gen can initially generate connector ACL snippets and advertise-routes flags for Github and AWS based on their public IP / domain data. Updates ENG-2425 Signed-off-by: James Tucker <james@tailscale.com>
- Loading branch information
Showing
5 changed files
with
255 additions
and
0 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# connector-gen | ||
|
||
Generate Tailscale app connector configuration details from third party data. | ||
|
||
Tailscale app connectors are used to dynamically route traffic for domain names | ||
via specific nodes on a tailnet. For larger upstream domains this may involve a | ||
large number of domains or routes, and fully dynamic discovery may be slower or | ||
involve more manual labor than ideal. This can be accelerated by | ||
pre-configuration of the associated routes, based on data provided by the | ||
target providers, which can be used to set precise `autoApprovers` routes, and | ||
also to pre-populate the subnet routes via `--advertise-routes` avoiding | ||
frequent routing reconfiguration that may otherwise occur while routes are | ||
first being discovered and advertised by the connectors. | ||
|
||
|
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
// Copyright (c) Tailscale Inc & AUTHORS | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
package main | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"go4.org/netipx" | ||
) | ||
|
||
func advertiseRoutes(set *netipx.IPSet) { | ||
fmt.Println() | ||
prefixes := set.Prefixes() | ||
pfxs := make([]string, 0, len(prefixes)) | ||
for _, pfx := range prefixes { | ||
pfxs = append(pfxs, pfx.String()) | ||
} | ||
fmt.Printf("--advertise-routes=%s", strings.Join(pfxs, ",")) | ||
fmt.Println() | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
// Copyright (c) Tailscale Inc & AUTHORS | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"net/netip" | ||
|
||
"go4.org/netipx" | ||
) | ||
|
||
// See https://docs.aws.amazon.com/vpc/latest/userguide/aws-ip-ranges.html | ||
|
||
type AWSMeta struct { | ||
SyncToken string `json:"syncToken"` | ||
CreateDate string `json:"createDate"` | ||
Prefixes []struct { | ||
IPPrefix string `json:"ip_prefix"` | ||
Region string `json:"region"` | ||
Service string `json:"service"` | ||
NetworkBorderGroup string `json:"network_border_group"` | ||
} `json:"prefixes"` | ||
Ipv6Prefixes []struct { | ||
Ipv6Prefix string `json:"ipv6_prefix"` | ||
Region string `json:"region"` | ||
Service string `json:"service"` | ||
NetworkBorderGroup string `json:"network_border_group"` | ||
} `json:"ipv6_prefixes"` | ||
} | ||
|
||
func aws() { | ||
r, err := http.Get("https://ip-ranges.amazonaws.com/ip-ranges.json") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer r.Body.Close() | ||
|
||
var aws AWSMeta | ||
if err := json.NewDecoder(r.Body).Decode(&aws); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
var ips netipx.IPSetBuilder | ||
|
||
for _, prefix := range aws.Prefixes { | ||
ips.AddPrefix(netip.MustParsePrefix(prefix.IPPrefix)) | ||
} | ||
for _, prefix := range aws.Ipv6Prefixes { | ||
ips.AddPrefix(netip.MustParsePrefix(prefix.Ipv6Prefix)) | ||
} | ||
|
||
set, err := ips.IPSet() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
fmt.Println(`"routes": [`) | ||
for _, pfx := range set.Prefixes() { | ||
fmt.Printf(`"%s": ["tag:connector"],%s`, pfx.String(), "\n") | ||
} | ||
fmt.Println(`]`) | ||
|
||
advertiseRoutes(set) | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) Tailscale Inc & AUTHORS | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
// connector-gen is a tool to generate app connector configuration and flags from service provider address data. | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
) | ||
|
||
func help() { | ||
fmt.Fprintf(os.Stderr, "Usage: %s [help|github|aws] [subcommand-arguments]\n", os.Args[0]) | ||
} | ||
|
||
func main() { | ||
if len(os.Args) < 2 { | ||
help() | ||
os.Exit(128) | ||
} | ||
|
||
switch os.Args[1] { | ||
case "help", "-h", "--help": | ||
help() | ||
os.Exit(0) | ||
case "github": | ||
github() | ||
case "aws": | ||
aws() | ||
default: | ||
help() | ||
os.Exit(128) | ||
} | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
// Copyright (c) Tailscale Inc & AUTHORS | ||
// SPDX-License-Identifier: BSD-3-Clause | ||
|
||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"log" | ||
"net/http" | ||
"net/netip" | ||
"slices" | ||
"strings" | ||
|
||
"go4.org/netipx" | ||
) | ||
|
||
// See https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-githubs-ip-addresses | ||
|
||
type GithubMeta struct { | ||
VerifiablePasswordAuthentication bool `json:"verifiable_password_authentication"` | ||
SSHKeyFingerprints struct { | ||
Sha256Ecdsa string `json:"SHA256_ECDSA"` | ||
Sha256Ed25519 string `json:"SHA256_ED25519"` | ||
Sha256Rsa string `json:"SHA256_RSA"` | ||
} `json:"ssh_key_fingerprints"` | ||
SSHKeys []string `json:"ssh_keys"` | ||
Hooks []string `json:"hooks"` | ||
Web []string `json:"web"` | ||
API []string `json:"api"` | ||
Git []string `json:"git"` | ||
GithubEnterpriseImporter []string `json:"github_enterprise_importer"` | ||
Packages []string `json:"packages"` | ||
Pages []string `json:"pages"` | ||
Importer []string `json:"importer"` | ||
Actions []string `json:"actions"` | ||
Dependabot []string `json:"dependabot"` | ||
Domains struct { | ||
Website []string `json:"website"` | ||
Codespaces []string `json:"codespaces"` | ||
Copilot []string `json:"copilot"` | ||
Packages []string `json:"packages"` | ||
} `json:"domains"` | ||
} | ||
|
||
func github() { | ||
r, err := http.Get("https://api.github.com/meta") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
var ghm GithubMeta | ||
|
||
if err := json.NewDecoder(r.Body).Decode(&ghm); err != nil { | ||
log.Fatal(err) | ||
} | ||
r.Body.Close() | ||
|
||
var ips netipx.IPSetBuilder | ||
|
||
var lists []string | ||
lists = append(lists, ghm.Hooks...) | ||
lists = append(lists, ghm.Web...) | ||
lists = append(lists, ghm.API...) | ||
lists = append(lists, ghm.Git...) | ||
lists = append(lists, ghm.GithubEnterpriseImporter...) | ||
lists = append(lists, ghm.Packages...) | ||
lists = append(lists, ghm.Pages...) | ||
lists = append(lists, ghm.Importer...) | ||
lists = append(lists, ghm.Actions...) | ||
lists = append(lists, ghm.Dependabot...) | ||
|
||
for _, s := range lists { | ||
ips.AddPrefix(netip.MustParsePrefix(s)) | ||
} | ||
|
||
set, err := ips.IPSet() | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
fmt.Println(`"routes": [`) | ||
for _, pfx := range set.Prefixes() { | ||
fmt.Printf(`"%s": ["tag:connector"],%s`, pfx.String(), "\n") | ||
} | ||
fmt.Println(`]`) | ||
|
||
fmt.Println() | ||
|
||
var domains []string | ||
domains = append(domains, ghm.Domains.Website...) | ||
domains = append(domains, ghm.Domains.Codespaces...) | ||
domains = append(domains, ghm.Domains.Copilot...) | ||
domains = append(domains, ghm.Domains.Packages...) | ||
slices.Sort(domains) | ||
domains = slices.Compact(domains) | ||
|
||
var bareDomains []string | ||
for _, domain := range domains { | ||
trimmed := strings.TrimPrefix(domain, "*.") | ||
if trimmed != domain { | ||
bareDomains = append(bareDomains, trimmed) | ||
} | ||
} | ||
domains = append(domains, bareDomains...) | ||
slices.Sort(domains) | ||
domains = slices.Compact(domains) | ||
|
||
fmt.Println(`"domains": [`) | ||
for _, domain := range domains { | ||
fmt.Printf(`"%s",%s`, domain, "\n") | ||
} | ||
fmt.Println(`]`) | ||
|
||
advertiseRoutes(set) | ||
} |