diff options
| author | Fran Bull <fran@tailscale.com> | 2024-05-02 12:27:59 -0700 |
|---|---|---|
| committer | Fran Bull <fran@tailscale.com> | 2024-05-02 12:27:59 -0700 |
| commit | b178c46be71c20246b1145b7c5e6f7e205d80872 (patch) | |
| tree | 522c42e007c1374d2da812f7c8ed52e7efc7ddd0 | |
| parent | 96712e10a77302427514255411dbfdf2c208d2f9 (diff) | |
| download | tailscale-fran/appc-ensmallen-gh-preset.tar.xz tailscale-fran/appc-ensmallen-gh-preset.zip | |
cmd/connector-gen: reduce the routes for githubfran/appc-ensmallen-gh-preset
This script produces output that contains thousands of routes. That's
more than really works well for a lot of tailnets, so reduce the number
of routes, while still enabling use of GitHub's enterprise "allowed IPs"
feature for the app connector IP.
Updates tailscale/corp/#19424
Signed-off-by: Fran Bull <fran@tailscale.com>
| -rw-r--r-- | cmd/connector-gen/github.go | 144 |
1 files changed, 88 insertions, 56 deletions
diff --git a/cmd/connector-gen/github.go b/cmd/connector-gen/github.go index def40872d..83d7d2604 100644 --- a/cmd/connector-gen/github.go +++ b/cmd/connector-gen/github.go @@ -13,104 +13,136 @@ import ( "strings" "go4.org/netipx" + xmaps "golang.org/x/exp/maps" ) +// omitDomains are domains that appear in the github API /meta output +// that we do not need to have app connectors route traffic for (and +// to do so would result in advertising more routes than we want). +var omitDomains = map[string]bool{ + "*.githubassets.com": true, + "*.githubusercontent.com": true, + "*.windows.net": true, + "*.azureedge.net": true, +} + // See https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/about-githubs-ip-addresses +// GithubMeta is a subset of the response from the github APIs /meta endpoint. 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) +func (ghm GithubMeta) routesLists() [][]string { + return [][]string{ + ghm.Web, + ghm.API, + ghm.Git, + ghm.GithubEnterpriseImporter, + ghm.Packages, + ghm.Pages, } +} - var ghm GithubMeta +func (ghm GithubMeta) domainsLists() [][]string { + return [][]string{ + ghm.Domains.Website, + ghm.Domains.Codespaces, + ghm.Domains.Copilot, + } +} - if err := json.NewDecoder(r.Body).Decode(&ghm); err != nil { +func (ghm GithubMeta) routes() *netipx.IPSet { + var ips netipx.IPSetBuilder + for _, routes := range ghm.routesLists() { + for _, r := range routes { + ips.AddPrefix(netip.MustParsePrefix(r)) + } + } + set, err := ips.IPSet() + if err != nil { log.Fatal(err) } - r.Body.Close() + return set +} - var ips netipx.IPSetBuilder +func (ghm GithubMeta) domains() []string { + ds := map[string]bool{} + for _, list := range ghm.domainsLists() { + for _, d := range list { + if !omitDomains[d] { + ds[d] = true + } + } + } + return xmaps.Keys(ds) +} - 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...) +type Output struct { + Routes []netip.Prefix `json:"routes"` + Domains []string `json:"domains"` +} - for _, s := range lists { - ips.AddPrefix(netip.MustParsePrefix(s)) +func (o Output) format() []byte { + s, err := json.MarshalIndent(o, "", " ") + if err != nil { + log.Fatal(err) } + return s +} - set, err := ips.IPSet() +// github prints app connector config to standard out. +// The /meta github endpoint lists the routes and domains needed to use GitHub. It +// lists thousands of routes, and includes broad wildcard domains like *.microsoft.com. +// Not all tailnets function well with an app connector that's advertising thousands of +// routes. +// GitHub has an enterprise "allowed IPs only" feature. The goal of this script is +// to capture only the domains and routes needed to configure an app connector so that +// users of that app connector can enable that GitHub feature pointing at the app connector +// IP address and have github work. +// We don't know exactly which routes and domains are needed, but I got an email from GitHub +// support saying that only the routes provided in 'web', 'api', and 'git' are needed, +// but that doesn't seem very likely, surely users of eg private packages will +// need to be coming from an allowed IP? Still, attempt to be reasonably restrictive. +func github() { + r, err := http.Get("https://api.github.com/meta") if err != nil { log.Fatal(err) } - fmt.Println(`"routes": [`) - for _, pfx := range set.Prefixes() { - fmt.Printf(`"%s": ["tag:connector"],%s`, pfx.String(), "\n") + var ghm GithubMeta + if err := json.NewDecoder(r.Body).Decode(&ghm); err != nil { + log.Fatal(err) } - fmt.Println(`]`) - - fmt.Println() + r.Body.Close() 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 { + for _, domain := range ghm.domains() { + domains = append(domains, domain) trimmed := strings.TrimPrefix(domain, "*.") if trimmed != domain { - bareDomains = append(bareDomains, trimmed) + domains = append(domains, 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(`]`) + set := ghm.routes() + + fmt.Println(string(Output{ + Routes: set.Prefixes(), + Domains: domains, + }.format())) advertiseRoutes(set) } |
