Skip to content

Commit

Permalink
cli: add flag to update secrets and trigger workflow run
Browse files Browse the repository at this point in the history
  • Loading branch information
joergmis committed Nov 11, 2023
1 parent f9846b2 commit 4767c64
Show file tree
Hide file tree
Showing 4 changed files with 207 additions and 100 deletions.
218 changes: 159 additions & 59 deletions cmd/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,110 +3,210 @@ package cmd
import (
"bytes"
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/http"
"sync"

"github.com/go-chi/chi"
"github.com/go-chi/chi/v5"
"github.com/google/go-github/v56/github"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/nacl/box"
"golang.org/x/oauth2"
"golang.org/x/oauth2/google"
"google.golang.org/api/gmail/v1"
)

var (
renewGmailToken bool
renewSheetsToken bool
config *oauth2.Config
renewGmailToken bool
renewSheetsToken bool
updateEnvironmentSecrets bool
config *oauth2.Config

updateCmd = &cobra.Command{
Use: "update",
Short: "Renew the tokens and update them in the config file",
Run: func(cmd *cobra.Command, args []string) {
credentialsKey := "credentials"
if renewGmailToken || renewSheetsToken {
if !viper.IsSet("credentials") {
cobra.CheckErr(errors.New("no sheets token file configured"))
}

wg := &sync.WaitGroup{}
// set up a waitgroup to make sure that - in case we update both
// tokens - we don't overwrite the global oauth object. Earlier
// this was not necessary because it required the input from the
// user
wg := &sync.WaitGroup{}

// setup a server to extract the auth token from the url
// the url itself can be adjusted in the 'credentials' - there is a
// field "redirect" or something like that
r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
defer wg.Done()

scope := r.URL.Query().Get("scope")
code := r.URL.Query().Get("code")

token, err := config.Exchange(context.TODO(), code)
if err != nil {
_, _ = w.Write([]byte("failed to get token from code: " + err.Error()))
}

var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(token); err != nil {
_, _ = w.Write([]byte("failed to encode token to json: " + err.Error()))
}

switch scope {
case "https://www.googleapis.com/auth/gmail.compose":
viper.Set("gmail_token", buf.String())
_, _ = w.Write([]byte("updated gmail token"))

case "https://www.googleapis.com/auth/spreadsheets":
viper.Set("sheets_token", buf.String())
_, _ = w.Write([]byte("updated sheets token"))

default:
_, _ = w.Write([]byte("unknown scope: " + scope))
}
})

go func() {
_ = http.ListenAndServe(":3333", r)
}()

b := []byte(viper.GetString("credentials"))

if renewSheetsToken {
var err error
wg.Add(1)
config, err = google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
if err != nil {
cobra.CheckErr(errors.Wrap(err, "parse sheets client secret"))
}
fmt.Println("=> open this link in your browser: " + config.AuthCodeURL("state-token", oauth2.AccessTypeOffline))
wg.Wait()
log.Println("=> generated new sheets token")

fmt.Println("=> token can be updated here: " + viper.GetString("secrets_url"))
}

if !viper.IsSet(credentialsKey) {
cobra.CheckErr(errors.New("no sheets token file configured"))
}
if renewGmailToken {
var err error
wg.Add(1)
config, err = google.ConfigFromJSON(b, gmail.GmailComposeScope)
if err != nil {
cobra.CheckErr(errors.Wrap(err, "parse gmail client secret"))
}
fmt.Println("=> open this link in your browser: " + config.AuthCodeURL("state-token", oauth2.AccessTypeOffline))
wg.Wait()
log.Println("=> generated new gmail token")
}

b := []byte(viper.GetString(credentialsKey))
if err := viper.WriteConfig(); err != nil {
cobra.CheckErr(errors.Wrap(err, "update config with new tokens"))
}
}

r := chi.NewRouter()
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
defer wg.Done()
if updateEnvironmentSecrets {
if !viper.IsSet("github_token") {
cobra.CheckErr(errors.New("no github token configured"))
}

scope := r.URL.Query().Get("scope")
code := r.URL.Query().Get("code")
// the repo id can be found in the page source of the repository:
// <meta content="your-repo-id" name="octolytics-dimension-repository_id" />
ctx := context.TODO()
client := github.NewClient(nil).WithAuthToken(viper.GetString("github_token"))

token, err := config.Exchange(context.TODO(), code)
pub, _, err := client.Actions.GetEnvPublicKey(ctx, 646451604, "prod")
if err != nil {
_, _ = w.Write([]byte("failed to get token from code: " + err.Error()))
cobra.CheckErr(errors.Wrap(err, "get environment public key"))
}

var buf bytes.Buffer
if err := json.NewEncoder(&buf).Encode(token); err != nil {
_, _ = w.Write([]byte("failed to encode token to json: " + err.Error()))
if !viper.IsSet("gmail_token") || !viper.IsSet("sheets_token") {
cobra.CheckErr(errors.New("gmail oder sheets token is missing in config"))
}

switch scope {
case "https://www.googleapis.com/auth/gmail.compose":
viper.Set("gmail_token", buf.String())
_, _ = w.Write([]byte("updated gmail token"))

case "https://www.googleapis.com/auth/spreadsheets":
viper.Set("sheets_token", buf.String())
_, _ = w.Write([]byte("updated sheets token"))

default:
_, _ = w.Write([]byte("unknown scope: " + scope))
secret, err := encrypt(viper.GetString("gmail_token"), pub.GetKey())
if err != nil {
cobra.CheckErr(errors.Wrap(err, "encrypt gmail secret"))
}
})

go func() {
_ = http.ListenAndServe(":3333", r)
}()

if renewSheetsToken {
var err error
wg.Add(1)
config, err = google.ConfigFromJSON(b, "https://www.googleapis.com/auth/spreadsheets")
_, err = client.Actions.CreateOrUpdateEnvSecret(ctx, 646451604, "prod", &github.EncryptedSecret{
Name: "GMAIL",
KeyID: *pub.KeyID,
EncryptedValue: secret,
})
if err != nil {
cobra.CheckErr(errors.Wrap(err, "parse sheets client secret"))
cobra.CheckErr(errors.Wrap(err, "update gmail token in 'prod' env"))
}
fmt.Println("=> open this link in your browser: " + config.AuthCodeURL("state-token", oauth2.AccessTypeOffline))
wg.Wait()
log.Println("=> generated new sheets token")
}

if renewGmailToken {
var err error
wg.Add(1)
config, err = google.ConfigFromJSON(b, gmail.GmailComposeScope)
secret, err = encrypt(viper.GetString("sheets_token"), pub.GetKey())
if err != nil {
cobra.CheckErr(errors.Wrap(err, "parse gmail client secret"))
cobra.CheckErr(errors.Wrap(err, "encrypt sheets secret"))
}
_, err = client.Actions.CreateOrUpdateEnvSecret(ctx, 646451604, "prod", &github.EncryptedSecret{
Name: "SHEETS",
KeyID: *pub.KeyID,
EncryptedValue: secret,
})
if err != nil {
cobra.CheckErr(errors.Wrap(err, "update sheets token in 'prod' env"))
}
fmt.Println("=> open this link in your browser: " + config.AuthCodeURL("state-token", oauth2.AccessTypeOffline))
wg.Wait()
log.Println("=> generated new gmail token")
}

if err := viper.WriteConfig(); err != nil {
cobra.CheckErr(errors.Wrap(err, "update config with new tokens"))
_, err = client.Actions.CreateWorkflowDispatchEventByFileName(ctx,
"startup-nights",
"functions",
"deploy.yml",
github.CreateWorkflowDispatchEventRequest{
Ref: "main",
},
)
if err != nil {
cobra.CheckErr(errors.Wrap(err, "trigger workflow run"))
}
}

fmt.Println("=> token can be updated here: " + viper.GetString("secrets_url"))
},
}
)

func encrypt(secret, pubkey string) (string, error) {
// https://jefflinse.io/posts/encrypting-github-secrets-using-go/
b, err := base64.StdEncoding.DecodeString(pubkey)
if err != nil {
return "", errors.Wrap(err, "decode public key to base64")
}
recipientKey := new([32]byte)
copy(recipientKey[:], b)
pubKey, privKey, err := box.GenerateKey(rand.Reader)
if err != nil {
return "", errors.Wrap(err, "generate key from random data")
}

nonceHash, err := blake2b.New(24, nil)
if err != nil {
return "", errors.Wrap(err, "create nonce hash")
}

nonceHash.Write(pubKey[:])
nonceHash.Write(recipientKey[:])

nonce := new([24]byte)
copy(nonce[:], nonceHash.Sum(nil))

out := box.Seal(pubKey[:], []byte(secret), nonce, recipientKey, privKey)
return base64.StdEncoding.EncodeToString(out), nil
}

func init() {
tokenCmd.AddCommand(updateCmd)
updateCmd.PersistentFlags().BoolVar(&renewGmailToken, "gmail", false, "A help for foo")
updateCmd.PersistentFlags().BoolVar(&renewSheetsToken, "sheets", false, "A help for foo")
updateCmd.PersistentFlags().BoolVar(&updateEnvironmentSecrets, "update-secrets", false, "Update the github actions environment secrets")
}
10 changes: 5 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@ module github.com/startup-nights/functions
go 1.21

require (
github.com/go-chi/chi v1.5.5
github.com/go-chi/chi/v5 v5.0.10
github.com/google/go-github/v56 v56.0.0
github.com/pkg/errors v0.9.1
github.com/spf13/cobra v1.8.0
github.com/spf13/viper v1.17.0
golang.org/x/crypto v0.15.0
golang.org/x/oauth2 v0.14.0
google.golang.org/api v0.150.0
gopkg.in/ini.v1 v1.67.0
)

require (
Expand All @@ -18,6 +19,7 @@ require (
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/s2a-go v0.1.7 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
Expand All @@ -33,12 +35,9 @@ require (
github.com/spf13/afero v1.10.0 // indirect
github.com/spf13/cast v1.5.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/testify v1.8.4 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.opencensus.io v0.24.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/exp v0.0.0-20231110203233-9a3e6036ecaa // indirect
golang.org/x/net v0.18.0 // indirect
golang.org/x/sys v0.14.0 // indirect
Expand All @@ -47,5 +46,6 @@ require (
google.golang.org/genproto/googleapis/rpc v0.0.0-20231106174013-bbf56f31fb17 // indirect
google.golang.org/grpc v1.59.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
Loading

0 comments on commit 4767c64

Please sign in to comment.