diff --git a/lib/api/application.go b/lib/api/application.go index 2be9d9db..223d6d48 100644 --- a/lib/api/application.go +++ b/lib/api/application.go @@ -100,7 +100,15 @@ func SetupApplication(app types.Application) types.ResponseError { return types.NewResErr(500, "git init unsuccessful", err) } - _, err = docker.ExecProcess(app.GetContainerID(), []string{"git", "remote", "add", "origin", app.GetGitRepositoryURL()}) + var cloneURL string + if len(app.GetGitAccessToken()) > 0 { + split := strings.Split(app.GetGitRepositoryURL(), "//") + cloneURL = fmt.Sprintf("https://oauth2:%s@%s", app.GetGitAccessToken() , split[1]) + } else { + cloneURL = app.GetGitRepositoryURL() + } + + _, err = docker.ExecProcess(app.GetContainerID(), []string{"git", "remote", "add", "origin", cloneURL}) if err != nil { return types.NewResErr(500, "setting remote unsuccessful", err) } diff --git a/lib/factory/application_manager.go b/lib/factory/application_manager.go index d407275a..d466b661 100644 --- a/lib/factory/application_manager.go +++ b/lib/factory/application_manager.go @@ -2,6 +2,10 @@ package factory import ( "context" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "encoding/base64" "github.com/google/go-github/v41/github" "github.com/sdslabs/gasper/configs" @@ -125,7 +129,7 @@ func NewApplicationFactory(bindings pb.ApplicationFactoryServer) *grpc.Server { } // CreateGithubRepository returns a git clone URL after creating a new repository -func CreateGithubRepository(repoName string) (*types.RepositoryResponse, error) { +func CreateGithubRepository(repoName string) (*types.ApplicationRemote, error) { tc := oauth2.NewClient( context.Background(), oauth2.StaticTokenSource( @@ -140,12 +144,17 @@ func CreateGithubRepository(repoName string) (*types.RepositoryResponse, error) Private: github.Bool(true), } repo, _, err := client.Repositories.Create(context.Background(), "", repo) - response := &types.RepositoryResponse{ - CloneURL: *repo.CloneURL, - PAT: configs.GithubConfig.PAT, - Username: configs.GithubConfig.Username, - Repository: repoName, - Email: configs.GithubConfig.Email, + response := &types.ApplicationRemote{ + GitURL: *repo.CloneURL, } return response, err } + +// Encrypt encrypts the PAT using the public key +func Encrypt(key rsa.PublicKey) (string, error) { + label := []byte("OAEP Encrypted") + rng := rand.Reader + secretMessage := configs.GithubConfig.PAT + ciphertext, err := rsa.EncryptOAEP(sha256.New(), rng, &key, []byte(secretMessage), label) + return base64.StdEncoding.EncodeToString(ciphertext), err +} diff --git a/services/master/controllers/application.go b/services/master/controllers/application.go index d58fc916..e4b9d86b 100644 --- a/services/master/controllers/application.go +++ b/services/master/controllers/application.go @@ -1,6 +1,8 @@ package controllers import ( + "bytes" + "encoding/json" "errors" "fmt" "strconv" @@ -303,3 +305,23 @@ func FetchMetrics(c *gin.Context) { "data": metricsRecord, }) } + +// FetchAppRemote retrieves the remote URL of an application +func FetchAppRemote(c *gin.Context) { + appName := c.Param("app") + config, err := mongo.FetchSingleApp(appName) + if err != nil { + c.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + } + + response := &types.ApplicationRemote{ + GitURL: config.Git.RepoURL, + } + + responseBody := new(bytes.Buffer) + json.NewEncoder(responseBody).Encode(response) + c.Data(200, "application/json", responseBody.Bytes()) +} \ No newline at end of file diff --git a/services/master/controllers/github.go b/services/master/controllers/github.go index 73efd3fd..3a834f10 100644 --- a/services/master/controllers/github.go +++ b/services/master/controllers/github.go @@ -7,6 +7,7 @@ import ( _ "io/ioutil" "github.com/gin-gonic/gin" + "github.com/sdslabs/gasper/configs" "github.com/sdslabs/gasper/lib/factory" "github.com/sdslabs/gasper/lib/utils" "github.com/sdslabs/gasper/services/master/middlewares" @@ -47,3 +48,36 @@ func CreateRepository(c *gin.Context) { json.NewEncoder(responseBody).Encode(response) c.Data(200, "application/json", responseBody.Bytes()) } + +func FetchPAT(c *gin.Context) { + raw, err := c.GetRawData() + if err != nil { + c.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + } + var data *types.EncryptKey= &types.EncryptKey{} + err = json.Unmarshal(raw, data) + if err != nil { + c.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + } + encryptedPAT, err := factory.Encrypt(data.PublicKey) + if err != nil { + c.AbortWithStatusJSON(400, gin.H{ + "success": false, + "error": err.Error(), + }) + } + response := &types.AccessToken{ + PAT: encryptedPAT, + Username: configs.GithubConfig.Username, + Email: configs.GithubConfig.Email, + } + responseBody := new(bytes.Buffer) + json.NewEncoder(responseBody).Encode(response) + c.Data(200, "application/json", responseBody.Bytes()) +} diff --git a/services/master/routes.go b/services/master/routes.go index e49921a2..dd3bc54d 100644 --- a/services/master/routes.go +++ b/services/master/routes.go @@ -57,7 +57,12 @@ func NewService() http.Handler { router.GET("/instances", m.AuthRequired(), c.FetchAllInstancesByUser) router.POST("/gctllogin", m.JWTGctl.MiddlewareFunc(), c.GctlLogin) - router.POST("/github", m.AuthRequired(), c.CreateRepository) + + github := router.Group("/github") + { + github.POST("", m.AuthRequired(), c.CreateRepository) + github.POST("/token", m.AuthRequired(), c.FetchPAT) + } app := router.Group("/apps") app.Use(m.AuthRequired()) @@ -72,6 +77,7 @@ func NewService() http.Handler { app.PATCH("/:app/transfer/:user", m.IsAppOwner, c.TransferApplicationOwnership) app.GET("/:app/term", m.IsAppOwner, c.DeployWebTerminal) app.GET("/:app/metrics", m.IsAppOwner, c.FetchMetrics) + app.GET("/:app/remote", m.IsAppOwner, c.FetchAppRemote) } db := router.Group("/dbs") diff --git a/types/application.go b/types/application.go index 5de7ba4d..8621a760 100644 --- a/types/application.go +++ b/types/application.go @@ -1,6 +1,7 @@ package types import ( + "crypto/rsa" "fmt" "math" "time" @@ -56,16 +57,14 @@ type Resources struct { CPU float64 `json:"cpu" bson:"cpu" valid:"float~Field 'cpu' inside field 'resources' should be of type float"` } +//RepositoryRequest is the request body for containing name of the application for repository creation type RepositoryRequest struct { Name string `json:"name" bson:"name" valid:"required~Field 'name' is required but was not provided,alphanum~Field 'name' should only have alphanumeric characters,stringlength(3|40)~Field 'name' should have length between 3 to 40 characters,lowercase~Field 'name' should have only lowercase characters"` } -type RepositoryResponse struct { - CloneURL string `json:"cloneurl" bson:"cloneurl"` - PAT string `json:"pat" bson:"pat"` - Username string `json:"username" bson:"username"` - Repository string `json:"repository" bson:"repository"` - Email string `json:"email" bson:"email"` +//EncryptKey is the request body for containing public key for encrypting the access token +type EncryptKey struct { + PublicKey rsa.PublicKey `json:"public_key"` } // ApplicationConfig is the configuration required for creating an application @@ -93,6 +92,21 @@ type ApplicationConfig struct { Success bool `json:"success,omitempty" bson:"-"` } +// ApplicationRemote is the struct containing the remote URL of the application's git repository +type ApplicationRemote struct { + GitURL string `json:"giturl" bson:"giturl"` +} + +//AccessToken is the struct containing the access token for the application's git repository +type AccessToken struct { + // PAT for pushing code to repository + PAT string `json:"pat" bson:"pat"` + // Username of Gasper Github user + Username string `json:"username" bson:"username"` + // Email id of Gasper Github user + Email string `json:"email" bson:"email"` +} + // GetName returns the application's name func (app *ApplicationConfig) GetName() string { return app.Name