summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorFran Bull <fran@tailscale.com>2024-05-02 12:27:59 -0700
committerFran Bull <fran@tailscale.com>2024-05-02 12:27:59 -0700
commitb178c46be71c20246b1145b7c5e6f7e205d80872 (patch)
tree522c42e007c1374d2da812f7c8ed52e7efc7ddd0
parent96712e10a77302427514255411dbfdf2c208d2f9 (diff)
downloadtailscale-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.go144
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)
}