diff --git a/cmd/tk/bootstrap_github.go b/cmd/tk/bootstrap_github.go index 35fb159c..64d8d5d8 100644 --- a/cmd/tk/bootstrap_github.go +++ b/cmd/tk/bootstrap_github.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "io/ioutil" + "net/url" "os" "path" "path/filepath" @@ -40,13 +41,13 @@ the bootstrap command will perform an upgrade if needed.`, export GITHUB_TOKEN= # Run bootstrap for a private repo owned by a GitHub organization - tk bootstrap github --owner= --repository= + bootstrap github --owner= --repository= # Run bootstrap for a public repository on a personal account - tk bootstrap github --owner= --repository= --private=false --personal=true + bootstrap github --owner= --repository= --private=false --personal=true # Run bootstrap for a private repo hosted on GitHub Enterprise - tk bootstrap github --owner= --repository= --hostname= + bootstrap github --owner= --repository= --hostname= `, RunE: bootstrapGitHubCmdRun, } @@ -86,6 +87,7 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { } ghURL := fmt.Sprintf("https://%s/%s/%s", ghHostname, ghOwner, ghRepository) + sshURL := fmt.Sprintf("ssh://git@%s/%s/%s", ghHostname, ghOwner, ghRepository) if ghOwner == "" || ghRepository == "" { return fmt.Errorf("owner and repository are required") } @@ -165,18 +167,30 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { } } - // create or update auth secret - // TODO: replace this with SSH deploy key - if err := generateBasicAuth(ctx, namespace, namespace, "git", ghToken); err != nil { - return err + // setup SSH deploy key + if shouldCreateGitHubDeployKey(ctx, kubeClient, namespace) { + logAction("configuring deploy key") + u, err := url.Parse(sshURL) + if err != nil { + return fmt.Errorf("git URL parse failed: %w", err) + } + + key, err := generateGitHubDeployKey(ctx, kubeClient, u, namespace) + if err != nil { + return fmt.Errorf("generating deploy key failed: %w", err) + } + + if err := createGitHubDeployKey(ctx, key, ghHostname, ghOwner, ghRepository, ghToken, ghPersonal); err != nil { + return nil + } + logSuccess("deploy key configured") } - logSuccess("authentication configured") // configure repo synchronization if isInstall { // generate source and kustomization manifests logAction("generating sync manifests") - if err := generateGitHubKustomization(ghURL, namespace, namespace, tmpDir, ghInterval); err != nil { + if err := generateGitHubKustomization(sshURL, namespace, namespace, tmpDir, ghInterval); err != nil { return err } @@ -205,12 +219,12 @@ func bootstrapGitHubCmdRun(cmd *cobra.Command, args []string) error { return nil } -func createGitHubRepository(ctx context.Context, hostname, owner, name, token string, isPrivate, isPersonal bool) error { - autoInit := true +func makeGitHubClient(hostname, token string) (*github.Client, error) { auth := github.BasicAuthTransport{ Username: "git", Password: token, } + gh := github.NewClient(auth.Client()) if hostname != ghDefaultHostname { baseURL := fmt.Sprintf("https://%s/api/v3/", hostname) @@ -218,9 +232,18 @@ func createGitHubRepository(ctx context.Context, hostname, owner, name, token st if g, err := github.NewEnterpriseClient(baseURL, uploadURL, auth.Client()); err == nil { gh = g } else { - return fmt.Errorf("github client error: %w", err) + return nil, fmt.Errorf("github client error: %w", err) } } + + return gh, nil +} + +func createGitHubRepository(ctx context.Context, hostname, owner, name, token string, isPrivate, isPersonal bool) error { + gh, err := makeGitHubClient(hostname, token) + if err != nil { + return err + } org := "" if !isPersonal { org = owner @@ -230,7 +253,8 @@ func createGitHubRepository(ctx context.Context, hostname, owner, name, token st return nil } - _, _, err := gh.Repositories.Create(ctx, org, &github.Repository{ + autoInit := true + _, _, err = gh.Repositories.Create(ctx, org, &github.Repository{ AutoInit: &autoInit, Name: &name, Private: &isPrivate, @@ -442,3 +466,67 @@ func shouldInstallGitHub(ctx context.Context, kubeClient client.Client, namespac return kustomization.Status.LastAppliedRevision == "" } + +func shouldCreateGitHubDeployKey(ctx context.Context, kubeClient client.Client, namespace string) bool { + namespacedName := types.NamespacedName{ + Namespace: namespace, + Name: namespace, + } + + var existing corev1.Secret + if err := kubeClient.Get(ctx, namespacedName, &existing); err != nil { + return true + } + return false +} + +func generateGitHubDeployKey(ctx context.Context, kubeClient client.Client, url *url.URL, namespace string) (string, error) { + pair, err := generateKeyPair(ctx) + if err != nil { + return "", err + } + + hostKey, err := scanHostKey(ctx, url) + if err != nil { + return "", err + } + + secret := corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: namespace, + Namespace: namespace, + }, + StringData: map[string]string{ + "identity": string(pair.PrivateKey), + "identity.pub": string(pair.PublicKey), + "known_hosts": string(hostKey), + }, + } + if err := upsertSecret(ctx, kubeClient, secret); err != nil { + return "", err + } + + return string(pair.PublicKey), nil +} + +func createGitHubDeployKey(ctx context.Context, key, hostname, owner, name, token string, isPersonal bool) error { + gh, err := makeGitHubClient(hostname, token) + if err != nil { + return err + } + keyName := fmt.Sprintf("tk-%s", namespace) + org := "" + if !isPersonal { + org = owner + } + isReadOnly := true + _, _, err = gh.Repositories.CreateKey(ctx, org, name, &github.Key{ + Title: &keyName, + Key: &key, + ReadOnly: &isReadOnly, + }) + if err != nil { + return fmt.Errorf("github create deploy key error: %w", err) + } + return nil +} diff --git a/go.mod b/go.mod index 1d0ca0cc..e0d48ecb 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,8 @@ require ( github.com/blang/semver v3.5.1+incompatible github.com/fluxcd/kustomize-controller v0.0.1-beta.1 github.com/fluxcd/source-controller v0.0.1-beta.1 + github.com/go-git/go-git/v5 v5.0.0 + github.com/google/go-github/v32 v32.0.0 github.com/manifoldco/promptui v0.7.0 github.com/spf13/cobra v1.0.0 golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 diff --git a/go.sum b/go.sum index 7d9cdf05..29a312b9 100644 --- a/go.sum +++ b/go.sum @@ -95,6 +95,7 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:z github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/containerd/containerd v1.3.2 h1:ForxmXkA6tPIvffbrDAcPUIB32QgXkt2XFj+F0UxetA= github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41/go.mod h1:Dq467ZllaHgAtVp4p1xUQWBrFXR9s/wyoTpG8zOJGkY= @@ -125,6 +126,7 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/daviddengcn/go-colortext v0.0.0-20160507010035-511bcaf42ccd/go.mod h1:dv4zxwHi5C/8AeI+4gX4dCWOIvNi7I6JCSX0HvlKPgE= +github.com/deislabs/oras v0.8.1 h1:If674KraJVpujYR00rzdi0QAmW4BxzMJPVAZJKuhQ0c= github.com/deislabs/oras v0.8.1/go.mod h1:Mx0rMSbBNaNfY9hjpccEnxkOqJL6KGjtxNHPLC4G4As= github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= @@ -491,8 +493,10 @@ github.com/onsi/gomega v1.8.1 h1:C5Dqfs/LeauYDX0jJXIe2SWmwCbGzx9yF8C8xy3Lh34= github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA= github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= +github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= @@ -667,8 +671,6 @@ golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975 h1:/Tl7pH94bvbAAHBdZJT947 golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073 h1:xMPOj6Pz6UipU1wXLkrtqpHbR0AVFnyPEQq/wRWz9lM= golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM= -golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -717,6 +719,7 @@ golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -814,6 +817,7 @@ google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0 h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=