From f4967764907b7b7a4733b469efb2061dc495b017 Mon Sep 17 00:00:00 2001 From: ssbkang Date: Mon, 1 Jul 2019 23:18:04 +1200 Subject: [PATCH] Introduced ClonePrivateRepositoryWithDeploymentKey and its handler --- api/git/git.go | 38 +++++++++++++++++-- .../handler/stacks/create_compose_stack.go | 4 ++ api/http/handler/stacks/create_swarm_stack.go | 20 +++++++--- api/http/handler/stacks/git.go | 7 +++- api/http/handler/stacks/handler.go | 1 + api/http/server.go | 1 + api/portainer.go | 1 + 7 files changed, 62 insertions(+), 10 deletions(-) diff --git a/api/git/git.go b/api/git/git.go index add688b01..95fa0d8d0 100644 --- a/api/git/git.go +++ b/api/git/git.go @@ -1,11 +1,12 @@ package git import ( - "net/url" - "strings" - + "golang.org/x/crypto/ssh" "gopkg.in/src-d/go-git.v4" "gopkg.in/src-d/go-git.v4/plumbing" + gitSsh "gopkg.in/src-d/go-git.v4/plumbing/transport/ssh" + "net/url" + "strings" ) // Service represents a service for managing Git. @@ -44,3 +45,34 @@ func cloneRepository(repositoryURL, referenceName string, destination string) er _, err := git.PlainClone(destination, false, options) return err } + +func (service *Service) ClonePrivateRepositoryWithDeploymentKey(repositoryURL, referenceName string, destination string, privateKeyPem []byte) error { + // url := "git@github.com:ssbkang/personal-development.git" + // directory := "personal-development" + // https://github.com/portainer/portainer-compose + + signer, _ := ssh.ParsePrivateKey(privateKeyPem) + auth := &gitSsh.PublicKeys{ + User: "git", + Signer: signer, + HostKeyCallbackHelper: gitSsh.HostKeyCallbackHelper{ + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + }, + } + + repositoryURL = strings.Replace(repositoryURL, "https://", "git@", 1) + repositoryURL += ".git" + + options := &git.CloneOptions{ + URL: repositoryURL, + RecurseSubmodules: git.DefaultSubmoduleRecursionDepth, + Auth: auth, + } + + if referenceName != "" { + options.ReferenceName = plumbing.ReferenceName(referenceName) + } + + _, err := git.PlainClone(destination, false, options) + return err +} diff --git a/api/http/handler/stacks/create_compose_stack.go b/api/http/handler/stacks/create_compose_stack.go index 66c9d91cb..f9a6b9136 100644 --- a/api/http/handler/stacks/create_compose_stack.go +++ b/api/http/handler/stacks/create_compose_stack.go @@ -87,11 +87,13 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter, return response.JSON(w, stack) } +// Update struc to cater for deployment key type composeStackFromGitRepositoryPayload struct { Name string RepositoryURL string RepositoryReferenceName string RepositoryAuthentication bool + RepositoryDeploymentKey string RepositoryUsername string RepositoryPassword string ComposeFilePathInRepository string @@ -145,11 +147,13 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite projectPath := handler.FileService.GetStackProjectPath(strconv.Itoa(int(stack.ID))) stack.ProjectPath = projectPath + // Add Deployment Key gitCloneParams := &cloneRepositoryParameters{ url: payload.RepositoryURL, referenceName: payload.RepositoryReferenceName, path: projectPath, authentication: payload.RepositoryAuthentication, + deploymentKey: payload.RepositoryDeploymentKey, username: payload.RepositoryUsername, password: payload.RepositoryPassword, } diff --git a/api/http/handler/stacks/create_swarm_stack.go b/api/http/handler/stacks/create_swarm_stack.go index 0832111e0..aa89a12f7 100644 --- a/api/http/handler/stacks/create_swarm_stack.go +++ b/api/http/handler/stacks/create_swarm_stack.go @@ -1,10 +1,6 @@ package stacks import ( - "net/http" - "strconv" - "strings" - "github.com/asaskevich/govalidator" httperror "github.com/portainer/libhttp/error" "github.com/portainer/libhttp/request" @@ -12,6 +8,9 @@ import ( "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/filesystem" "github.com/portainer/portainer/api/http/security" + "net/http" + "strconv" + "strings" ) type swarmStackFromFileContentPayload struct { @@ -92,6 +91,7 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r return response.JSON(w, stack) } +// Update struc to cater for deployment key type swarmStackFromGitRepositoryPayload struct { Name string SwarmID string @@ -99,6 +99,7 @@ type swarmStackFromGitRepositoryPayload struct { RepositoryURL string RepositoryReferenceName string RepositoryAuthentication bool + RepositoryDeploymentKey string RepositoryUsername string RepositoryPassword string ComposeFilePathInRepository string @@ -111,12 +112,18 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err if govalidator.IsNull(payload.SwarmID) { return portainer.Error("Invalid Swarm ID") } + + // Need to improve validators for SSH based URL and deployment key type + + /* if govalidator.IsNull(payload.RepositoryURL) || !govalidator.IsURL(payload.RepositoryURL) { return portainer.Error("Invalid repository URL. Must correspond to a valid URL format") } - if payload.RepositoryAuthentication && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) { - return portainer.Error("Invalid repository credentials. Username and password must be specified when authentication is enabled") + + if payload.RepositoryAuthentication && (govalidator.IsNull(payload.RepositoryDeploymentKey)) || (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) { + return portainer.Error("Invalid repository credentials or deploymenet key. Either Username & password or a deployment key must be specified when authentication is enabled") } + */ if govalidator.IsNull(payload.ComposeFilePathInRepository) { payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName } @@ -160,6 +167,7 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter, referenceName: payload.RepositoryReferenceName, path: projectPath, authentication: payload.RepositoryAuthentication, + deploymentKey: payload.RepositoryDeploymentKey, username: payload.RepositoryUsername, password: payload.RepositoryPassword, } diff --git a/api/http/handler/stacks/git.go b/api/http/handler/stacks/git.go index b3e2448f7..55ba22d67 100644 --- a/api/http/handler/stacks/git.go +++ b/api/http/handler/stacks/git.go @@ -5,13 +5,18 @@ type cloneRepositoryParameters struct { referenceName string path string authentication bool + deploymentKey string username string password string } func (handler *Handler) cloneGitRepository(parameters *cloneRepositoryParameters) error { - if parameters.authentication { + if parameters.authentication && parameters.username != "" && parameters.password != "" { return handler.GitService.ClonePrivateRepositoryWithBasicAuth(parameters.url, parameters.referenceName, parameters.path, parameters.username, parameters.password) } + if parameters.authentication && parameters.deploymentKey != "" { + deploymentKey, _ := handler.DeploymentKeyService.DeploymentKeyByName(parameters.deploymentKey) + return handler.GitService.ClonePrivateRepositoryWithDeploymentKey(parameters.url, parameters.referenceName, parameters.path, deploymentKey.PrivateKey) + } return handler.GitService.ClonePublicRepository(parameters.url, parameters.referenceName, parameters.path) } diff --git a/api/http/handler/stacks/handler.go b/api/http/handler/stacks/handler.go index caf181bef..09811bcea 100644 --- a/api/http/handler/stacks/handler.go +++ b/api/http/handler/stacks/handler.go @@ -16,6 +16,7 @@ type Handler struct { stackDeletionMutex *sync.Mutex requestBouncer *security.RequestBouncer *mux.Router + DeploymentKeyService portainer.DeploymentKeyService FileService portainer.FileService GitService portainer.GitService StackService portainer.StackService diff --git a/api/http/server.go b/api/http/server.go index d7ceba92b..ebb6949ed 100644 --- a/api/http/server.go +++ b/api/http/server.go @@ -182,6 +182,7 @@ func (server *Server) Start() error { stackHandler.FileService = server.FileService stackHandler.StackService = server.StackService stackHandler.EndpointService = server.EndpointService + stackHandler.DeploymentKeyService = server.DeploymentKeyService stackHandler.ResourceControlService = server.ResourceControlService stackHandler.SwarmStackManager = server.SwarmStackManager stackHandler.ComposeStackManager = server.ComposeStackManager diff --git a/api/portainer.go b/api/portainer.go index e79eb6e19..f6012de06 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -817,6 +817,7 @@ type ( GitService interface { ClonePublicRepository(repositoryURL, referenceName string, destination string) error ClonePrivateRepositoryWithBasicAuth(repositoryURL, referenceName string, destination, username, password string) error + ClonePrivateRepositoryWithDeploymentKey(repositoryURL, referenceName string, destination string, privateKeyPem []byte) error } // JobScheduler represents a service to run jobs on a periodic basis