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>
pull/10186/head
James Tucker 5 months ago committed by James Tucker
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…
Cancel
Save