diff --git a/access/fetch-keys/README.md b/access/fetch-keys/README.md new file mode 100644 index 0000000..21c6acf --- /dev/null +++ b/access/fetch-keys/README.md @@ -0,0 +1,14 @@ +Use the Github API to fetch configured SSH keys for a team. + +Build by (making sure your GOPATH environment variable is properly configured) - +``` +go get +go build +``` + +Run +``` +./fetch-keys --help +``` + +The tool uses an OAuth Token to connect to the Github API, make sure you've added a personal access token via your [Github account settings](https://github.com/settings/tokens) and have either properly edited the [authtoken.go](authtoken.go#L3) source file (!!! with caution), or pass the appropriate token via the command line. diff --git a/access/fetch-keys/authtoken.go b/access/fetch-keys/authtoken.go new file mode 100644 index 0000000..ad85a29 --- /dev/null +++ b/access/fetch-keys/authtoken.go @@ -0,0 +1,3 @@ +package main + +const AuthToken = "c90e06359175cfccc4db21f60c44b83d93ccbd28" diff --git a/access/fetch-keys/main.go b/access/fetch-keys/main.go new file mode 100644 index 0000000..ea6ca35 --- /dev/null +++ b/access/fetch-keys/main.go @@ -0,0 +1,243 @@ +/*- + * ---------------------------------------------------------------------------- + * "THE BEER-WARE LICENSE" (Revision 69): + * wrote this file. As long as you retain this notice you + * can do whatever you want with this stuff. If we meet some day, and you think + * this stuff is worth it, you can buy me a beer in return. -Shteryana Shopova + * ---------------------------------------------------------------------------- + */ + +package main + +import ( + "bytes" + "context" + "fmt" + "github.com/akamensky/argparse" + "github.com/google/go-github/github" // with go modules disabled + "golang.org/x/oauth2" + "io/ioutil" + "os" +) + +func main() { + + parser := argparse.NewParser("sshkeys", "Fetch SSH keys for a Github team members") + authToken := parser.String("a", "authtoken", &argparse.Options{Required: false, Help: "Github Auth token", Default: AuthToken}) + keysDir := parser.String("d", "directory", &argparse.Options{Required: false, Help: "Path where to store the key files", Default: "./"}) + ghOrganization := parser.String("o", "org", &argparse.Options{Required: false, Help: "Github Organization name", Default: "OpenFest"}) + fetchPgp := parser.Flag("p", "pgp-keys", &argparse.Options{Required: false, Help: "Fetch configured PGP key ids", Default: false}) + quiet := parser.Flag("q", "quiet", &argparse.Options{Required: false, Help: "Skip output to stdout", Default: false}) + ghTeam := parser.String("t", "team", &argparse.Options{Required: false, Help: "Github Team name, 'all' for all members of the organization", Default: "NOC"}) + verbose := parser.Flag("v", "verbose", &argparse.Options{Required: false, Help: "Verbose output: print keys to stdout", Default: false}) + + // Parse input + err := parser.Parse(os.Args) + if err != nil { + fmt.Print(parser.Usage(err)) + os.Exit(1) + } + + if *verbose == false { + fi, err := os.Lstat(*keysDir) + if err != nil { + fmt.Println(*keysDir, ": target directory error :", err) + os.Exit(1) + } else { + if fi.Mode().IsDir() == false { + fmt.Println(*keysDir, ": target directory error : not a directory - ", fi.Mode()) + os.Exit(1) + } + } + } + + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: *authToken}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := github.NewClient(tc) + + teamMembers := fetchUsers(client, ghOrganization, ghTeam) + for _, user := range teamMembers { + if *quiet == false { + fmt.Println("Fetching keys for", *user) + } + var sshKeys bytes.Buffer + var pgpKeys bytes.Buffer + + for nextPage := 0; ; { + + // list all teams an org for the current user + opt := &github.ListOptions{nextPage, 50} + keys, rsp, err := client.Users.ListKeys(ctx, *user, opt) + + if err != nil { + fmt.Println("client.Users.ListKeys error: ", err) + os.Exit(-1) + } + + if rsp == nil { + fmt.Println("Users.ListKeys returned empty response: ", err) + } + + for _, key := range keys { + if *verbose == true { + fmt.Println(*key.Key) + } + sshKeys.WriteString(*key.Key) + sshKeys.WriteString("\n") + } + + if rsp.NextPage == 0 || nextPage == rsp.NextPage { + break + } + nextPage = rsp.NextPage + } + + if *quiet == false && *verbose == false { + fmt.Println("Writing to", *keysDir+"/"+*user+".key") + } + err := ioutil.WriteFile(*keysDir+"/"+*user+".key", sshKeys.Bytes(), 0444) + if err != nil && *verbose == false { + fmt.Println(*user+".key error ", err) + } + + if *fetchPgp == true { + for nextPage := 0; ; { + + // list all teams an org for the current user + opt := &github.ListOptions{nextPage, 50} + keys, rsp, err := client.Users.ListGPGKeys(ctx, *user, opt) + + if err != nil { + fmt.Println("client.Users.ListGPGKeys error: ", err) + os.Exit(-1) + } + + if rsp == nil { + fmt.Println("Users.ListGPGKeys returned empty response: ", err) + } + + for _, key := range keys { + if *verbose == true { + fmt.Println(*key.KeyID) + } + pgpKeys.WriteString(*key.KeyID) + pgpKeys.WriteString("\n") + } + + if rsp.NextPage == 0 || nextPage == rsp.NextPage { + break + } + nextPage = rsp.NextPage + } + if *quiet == false && *verbose == false { + fmt.Println("Writing to", *keysDir+"/"+*user+".gpg") + } + err = ioutil.WriteFile(*keysDir+"/"+*user+".gpg", pgpKeys.Bytes(), 0444) + if err != nil && *verbose == false { + fmt.Println(*user+".gpg error ", err) + } + } + } + + os.Exit(0) +} + +func fetchUsers(client *github.Client, org *string, team *string) (teamMembers []*string) { + var targetTeam *github.Team + + if team == nil || *team == "all" { + for nextPage := 0; ; { + // list all members for the given organization's team + opt := &github.ListMembersOptions{ + PublicOnly: false, + ListOptions: github.ListOptions{nextPage, 50}, + } + + users, rsp, err := client.Organizations.ListMembers(context.Background(), *org, opt) + + if err != nil { + fmt.Println("client.Organizations.ListMembers ", err) + os.Exit(-1) + } + + if rsp == nil { + fmt.Println("client.Organizations.ListMembers: ", err) + } + + for _, user := range users { + teamMembers = append(teamMembers, user.Login) + } + + if rsp.NextPage == 0 || nextPage == rsp.NextPage { + break + } + nextPage = rsp.NextPage + } + } else { + for nextPage := 0; ; { + // list all teams for the specified org + opt := &github.ListOptions{nextPage, 50} + teams, rsp, err := client.Teams.ListTeams(context.Background(), *org, opt) + + if err != nil { + fmt.Println("client.ListTeams error: ", err) + os.Exit(-1) + } + + if rsp == nil { + fmt.Println("client.ListTeams returned empty response: ", err) + } + + for _, ghTeam := range teams { + if *ghTeam.Name == *team { + targetTeam = ghTeam + break + } + } + + if rsp.NextPage == 0 || nextPage == rsp.NextPage { + break + } + nextPage = rsp.NextPage + } + + if targetTeam == nil { + fmt.Println(*team, " team not found in ", *org) + os.Exit(2) + } + + for nextPage := 0; ; { + // list all members for the given organization's team + opt := &github.TeamListTeamMembersOptions{ + Role: "all", + ListOptions: github.ListOptions{nextPage, 50}, + } + + users, rsp, err := client.Teams.ListTeamMembers(context.Background(), *targetTeam.ID, opt) + + if err != nil { + fmt.Println("client.Teams.ListTeamMembers ", err) + os.Exit(-1) + } + + if rsp == nil { + fmt.Println("client.Teams.ListTeamMembers: ", err) + } + + for _, user := range users { + teamMembers = append(teamMembers, user.Login) + } + + if rsp.NextPage == 0 || nextPage == rsp.NextPage { + break + } + nextPage = rsp.NextPage + } + } + + return teamMembers +}