package main import ( "context" "encoding/base64" "flag" "fmt" "io/ioutil" "log" "os" "path/filepath" "strings" "github.com/google/go-github/github" "github.com/tcnksm/go-gitconfig" "golang.org/x/crypto/ssh" "golang.org/x/oauth2" ) var ( defaultPublicKeyPath = filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa.pub") ) func main() { sshkey := flag.String("ssh-key", defaultPublicKeyPath, "Path to the ssh public key to upload") flag.Parse() keyContent, keyTitle, err := readPublicKey(sshkey) if err != nil { log.Fatal(err) } keyToGitHub(keyContent, keyTitle) } // readPublicKey reads the public key, ensure it's in the proper // format to prevent sending incorrect data, and returns the content // of the key (the whole content) and the last field of the key that // will be used as the title. func readPublicKey(sshkey *string) (string, string, error) { // first, let's ensure it's a public key, so we don't upload // something incorrect. data, err := ioutil.ReadFile(*sshkey) if err != nil { return "", "", fmt.Errorf("failed to open %s: %v", *sshkey, err) } // we only want the content of the key to be parsed, not all the fields fields := strings.Fields(string(data)) if len(fields) < 2 { return "", "", fmt.Errorf("not enough fields in public key %s (%d)", *sshkey, len(fields)) } raw, err := base64.StdEncoding.DecodeString(fields[1]) if err != nil { return "", "", fmt.Errorf("failed to read the second field in the public key %s: %v", *sshkey, err) } _, err = ssh.ParsePublicKey(raw) if err != nil { return "", "", fmt.Errorf("%s is not a valid public key: %v", *sshkey, err) } return strings.TrimSuffix(string(data), "\n"), fields[2], nil } // If the key does not exists already on GitHub, upload it, using the // signature (last part of the key) as the title. func keyToGitHub(keyContent, keyTitle string) { user, err := gitconfig.Global("github.user") if err != nil { fmt.Fprintf(os.Stderr, "github: failed to get the username: %v", err) return } token, err := gitconfig.Global("github.tokensshtoforge") if err != nil { fmt.Fprintf(os.Stderr, "github: failed to get the token: %v", err) return } ctx := context.Background() ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) tc := oauth2.NewClient(ctx, ts) client := github.NewClient(tc) keys, _, err := client.Users.ListKeys(ctx, user, nil) if err != nil { fmt.Fprintf(os.Stderr, "github: failed to get the list of existing keys: %v", err) return } skip := false for _, k := range keys { ghKey := *k.Key + " " + keyTitle c := strings.Compare(ghKey, keyContent) if c == 0 { skip = true break } } if !skip { key := github.Key{ Key: &keyContent, Title: &keyTitle, } _, _, err := client.Users.CreateKey(ctx, &key) if err != nil { fmt.Fprintf(os.Stderr, "github: failed to upload the ssh public key: %+v", err) return } } }