Skip to content

Commit

Permalink
Merge pull request #14 from christianh814/dev
Browse files Browse the repository at this point in the history
DEV v0.0.3


* Switched from using token to using deploy key for repo access
* Kubernetes version now is a variable in the code.
  • Loading branch information
christianh814 authored Nov 9, 2021
2 parents a8bbaa3 + 6df52c4 commit 0e313fa
Show file tree
Hide file tree
Showing 8 changed files with 222 additions and 26 deletions.
6 changes: 4 additions & 2 deletions cmd/capi/capi.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ var CNIurl string = "https://docs.projectcalico.org/v3.20/manifests/calico.yaml"

var decUnstructured = yaml.NewDecodingSerializer(unstructured.UnstructuredJSONScheme)

var KubernetesVersion string = "v1.22.2"

// CreateAwsK8sInstance creates a Kubernetes cluster on AWS using CAPI and CAPI-AWS
func CreateAwsK8sInstance(kindkconfig string, clusterName *string, workdir string, awscreds map[string]string, capicfg string, createHaCluster bool, skipCloudFormation bool) (bool, error) {
// Export AWS settings as Env vars
Expand Down Expand Up @@ -133,7 +135,7 @@ func CreateAwsK8sInstance(kindkconfig string, clusterName *string, workdir strin
ClusterName: *clusterName,
ControlPlaneMachineCount: &cpMachineCount,
WorkerMachineCount: &workerMachineCount,
KubernetesVersion: "v1.22.2",
KubernetesVersion: KubernetesVersion,
TargetNamespace: "default",
}

Expand Down Expand Up @@ -349,7 +351,7 @@ func CreateDevelK8sInstance(kindkconfig string, clusterName *string, workdir str
ClusterName: *clusterName,
ControlPlaneMachineCount: &cpMachineCount,
WorkerMachineCount: &workerMachineCount,
KubernetesVersion: "v1.22.1",
KubernetesVersion: KubernetesVersion,
TargetNamespace: "default",
ProviderRepositorySource: &capiclient.ProviderRepositorySourceOptions{Flavor: "development"},
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/createCluster_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ doesn't create one for you).`,
}

// Git push newly exported YAML to GitOps repo
_, err = github.CommitAndPush(WorkDir+"/"+clusterName, ghToken, "exporting existing YAML")
privateKeyFile := WorkDir + "/" + clusterName + "_rsa"
_, err = github.CommitAndPush(WorkDir+"/"+clusterName, privateKeyFile, "exporting existing YAML")
if err != nil {
log.Fatal(err)
}
Expand Down
3 changes: 2 additions & 1 deletion cmd/createCluster_development.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ so beware. This create a local cluster for testing. PRE-PRE-ALPHA.`,
}

// Git push newly exported YAML to GitOps repo
_, err = github.CommitAndPush(WorkDir+"/"+clusterName, ghToken, "exporting existing YAML")
privateKeyFile := WorkDir + "/" + clusterName + "_rsa"
_, err = github.CommitAndPush(WorkDir+"/"+clusterName, privateKeyFile, "exporting existing YAML")
if err != nil {
log.Fatal(err)
}
Expand Down
171 changes: 157 additions & 14 deletions cmd/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,20 @@ package github

import (
"context"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"time"

"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport/http"
plumbingssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/google/go-github/v39/github"
log "github.com/sirupsen/logrus"
"golang.org/x/crypto/ssh"
"golang.org/x/oauth2"
)

Expand Down Expand Up @@ -38,36 +44,58 @@ func CreateRepo(name *string, token string, private *bool, workdir string) (bool
r := &github.Repository{Name: name, Private: private, Description: description, AutoInit: &autoInit}
repo, _, err := client.Repositories.Create(ctx, "", r)
if err != nil {
log.Fatal(err)
return false, "", err
}

// Create an SSHKeypair for the repo.
publicKeyBytes, err := generateSSHKeypair(*name, workdir)
if err != nil {
return false, "", err
}

// upload public sshkey as a deploy key
err = uploadDeployKey(publicKeyBytes, repo.GetOwner().GetLogin(), *name, client)
if err != nil {
return false, "", err
}

// Get the remote URL and set the name of the local copy
repoUrl := repo.GetCloneURL()
//repoUrl := repo.GetCloneURL()
repoUrl := repo.GetSSHURL()
localRepo := workdir + "/" + *name

// Maksure the localRepo is there
os.MkdirAll(localRepo, 0755)

// Read sshkey to do the clone
privateKeyFile := workdir + "/" + *name + "_rsa"
authKey, err := plumbingssh.NewPublicKeysFromFile("git", privateKeyFile, "")
if err != nil {
return false, "", err
}

// Clone the repo locally in the working dir (as localRepo)
_, err = git.PlainClone(localRepo, false, &git.CloneOptions{
URL: repoUrl,
Auth: &http.BasicAuth{
Username: "unused",
Password: token,
},
URL: repoUrl,
Auth: authKey,
/*
Auth: &http.BasicAuth{
Username: "unused",
Password: token,
},
*/
})

if err != nil {
log.Fatal(err)
return false, "", err
}

log.Info("Successfully created new repo: ", repoUrl)
return true, repoUrl, nil
}

func CommitAndPush(dir string, token string, msg string) (bool, error) {
// CommitAndPush commits and pushes changes to a github repo that has been changed locally
func CommitAndPush(dir string, privateKeyFile string, msg string) (bool, error) {
// Open the dir for commiting
repo, err := git.PlainOpen(dir)
if err != nil {
Expand Down Expand Up @@ -104,13 +132,22 @@ func CommitAndPush(dir string, token string, msg string) (bool, error) {
return false, err
}

// Read sshkey to do the clone
authKey, err := plumbingssh.NewPublicKeysFromFile("git", privateKeyFile, "")
if err != nil {
return false, err
}

//Push to repo
err = repo.Push(&git.PushOptions{
RemoteName: "origin",
Auth: &http.BasicAuth{
Username: "unused",
Password: token,
},
Auth: authKey,
/*
Auth: &http.BasicAuth{
Username: "unused",
Password: token,
},
*/
})

if err != nil {
Expand All @@ -122,3 +159,109 @@ func CommitAndPush(dir string, token string, msg string) (bool, error) {

return true, nil
}

// generateSSHKeypair generates an sshkeypair to use as a deploykey on Github
func generateSSHKeypair(clustername string, workdir string) ([]byte, error) {
key := workdir + "/" + clustername + "_rsa"
savePrivateFileTo := key
savePublicFileTo := key + ".pub"
bitSize := 4096

privateKey, err := generatePrivateKey(bitSize)
if err != nil {
return nil, err
}

publicKeyBytes, err := generatePublicKey(&privateKey.PublicKey)
if err != nil {
return nil, err
}

privateKeyBytes := encodePrivateKeyToPEM(privateKey)

err = writeKeyToFile(privateKeyBytes, savePrivateFileTo)
if err != nil {
return nil, err
}

err = writeKeyToFile([]byte(publicKeyBytes), savePublicFileTo)
if err != nil {
return nil, err
}
return publicKeyBytes, nil
}

// generatePrivateKey creates a RSA Private Key of specified byte size
func generatePrivateKey(bitSize int) (*rsa.PrivateKey, error) {
// Private Key generation
privateKey, err := rsa.GenerateKey(rand.Reader, bitSize)
if err != nil {
return nil, err
}

// Validate Private Key
err = privateKey.Validate()
if err != nil {
return nil, err
}

return privateKey, nil
}

// encodePrivateKeyToPEM encodes Private Key from RSA to PEM format
func encodePrivateKeyToPEM(privateKey *rsa.PrivateKey) []byte {
// Get ASN.1 DER format
privDER := x509.MarshalPKCS1PrivateKey(privateKey)

// pem.Block
privBlock := pem.Block{
Type: "RSA PRIVATE KEY",
Headers: nil,
Bytes: privDER,
}

// Private key in PEM format
privatePEM := pem.EncodeToMemory(&privBlock)

return privatePEM
}

// generatePublicKey take a rsa.PublicKey and return bytes suitable for writing to .pub file. Returns in the format "ssh-rsa ..."
func generatePublicKey(privatekey *rsa.PublicKey) ([]byte, error) {
publicRsaKey, err := ssh.NewPublicKey(privatekey)
if err != nil {
return nil, err
}

pubKeyBytes := ssh.MarshalAuthorizedKey(publicRsaKey)

return pubKeyBytes, nil
}

// writePemToFile writes keys to a file
func writeKeyToFile(keyBytes []byte, saveFileTo string) error {
err := ioutil.WriteFile(saveFileTo, keyBytes, 0600)
if err != nil {
return err
}

return nil
}

// uploadDeployKey uploads deploykey to GitHub
func uploadDeployKey(publicKeyBytes []byte, repoOwner string, name string, client *github.Client) error {
// Set up the github key object based on the key given to use as a []byte
mykey := string(publicKeyBytes)
key := &github.Key{
Key: &mykey,
}

// upload the deploykey to the repo
_, _, err := client.Repositories.CreateKey(context.TODO(), repoOwner, name, key)
if err != nil {
return err
}

// if we're here we should be okay
return nil
}
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ var CapiCfg string
// rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{
Use: "gokp",
Version: "v0.0.3",
Version: "v0.0.4",
Short: "GOKP installs a GitOps ready Kubernetes cluster",
Long: `GOKP creates a Kubernetes cluster using CAPI. It is
meant to be a GitOps native Kubernetes cluster ready to use.
Expand Down
35 changes: 29 additions & 6 deletions cmd/templates/templates.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package templates

import (
"encoding/base64"
"os"
"strings"

Expand Down Expand Up @@ -61,6 +62,7 @@ data:
- /spec/allocations
`

/*
var ArgoCdOverlayDefaultRepoSecret string = `apiVersion: v1
kind: Secret
metadata:
Expand All @@ -75,6 +77,21 @@ stringData:
password: {{.GitHubToken}}
{{ end }}
`
*/

var ArgoCdOverlayDefaultRepoSecret string = `apiVersion: v1
kind: Secret
metadata:
name: cluster-repo
namespace: argocd
labels:
argocd.argoproj.io/secret-type: repository
type: Opaque
data:
sshPrivateKey: {{.SSHPrivateKey}}
type: Z2l0
url: {{.ClusterGitOpsRepo}}
`

var ArgoCdComponetnsApplicationSetKustomize string = `resources:
- cluster-components.yaml
Expand Down Expand Up @@ -325,14 +342,19 @@ func CreateRepoSkel(name *string, workdir string, ghtoken string, gitopsrepo str
}

// Write out the argocd secret of the repo based on the vars and template
sshKeyFile, err := utils.B64EncodeFile(workdir + "/" + *name + "_rsa")
if err != nil {
return false, err
}
githubInfo := struct {
ClusterGitOpsRepo string
GitHubToken string
IsPrivate bool
SSHPrivateKey string
//IsPrivate bool
}{
ClusterGitOpsRepo: gitopsrepo,
GitHubToken: ghtoken,
IsPrivate: *private,
ClusterGitOpsRepo: base64.StdEncoding.EncodeToString([]byte(gitopsrepo)),
SSHPrivateKey: sshKeyFile,
//GitHubToken: ghtoken,
//IsPrivate: *private,
}
_, err = utils.WriteTemplate(ArgoCdOverlayDefaultRepoSecret, dir+"/"+"repo-secret.yaml", githubInfo)
if err != nil {
Expand Down Expand Up @@ -451,7 +473,8 @@ func CreateRepoSkel(name *string, workdir string, ghtoken string, gitopsrepo str

// Commit and push initialize skel
log.Info("Pushing initial skel repo structure")
_, err := github.CommitAndPush(repoDir, ghtoken, "initializing skel repo structure")
privateKeyFile := workdir + "/" + *name + "_rsa"
_, err := github.CommitAndPush(repoDir, privateKeyFile, "initializing skel repo structure")
if err != nil {
return false, err
}
Expand Down
26 changes: 26 additions & 0 deletions cmd/utils/utils.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package utils

import (
"bufio"
"encoding/base64"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -220,3 +222,27 @@ func CopyDir(source string, dest string) error {
}
return err
}

// B64EncodeFile returns the base64 encoding of a file as a string. The file must be a full path
func B64EncodeFile(file string) (string, error) {
// Open file on disk.
f, err := os.Open(file)
if err != nil {
return "", err
}
// be sure to close the file
defer f.Close()

// Read file into byte slice.
reader := bufio.NewReader(f)
content, err := ioutil.ReadAll(reader)
if err != nil {
return "", err
}

// Encode as base64.
encoded := base64.StdEncoding.EncodeToString(content)

// return result
return encoded, nil
}
Loading

0 comments on commit 0e313fa

Please sign in to comment.