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>main
parent
706e30d49e
commit
3a635db06e
@ -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. |
||||
|
||||
|
||||
@ -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() |
||||
} |
||||
@ -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) |
||||
} |
||||
@ -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) |
||||
} |
||||
} |
||||
@ -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) |
||||
} |
||||
Loading…
Reference in new issue