mirror of https://github.com/tailscale/tailscale/
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
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