feat(stack): Introduce SSH Key Based Deployment

pull/2972/head
ssbkang 2019-07-02 22:41:01 +12:00
parent 5d169ad827
commit ef800d5a7d
6 changed files with 74 additions and 70 deletions

View File

@ -47,10 +47,6 @@ func cloneRepository(repositoryURL, referenceName string, destination string) er
}
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",

View File

@ -36,8 +36,6 @@ func (handler *Handler) deploymentkeyCreate(w http.ResponseWriter, r *http.Reque
return &httperror.HandlerError{http.StatusConflict, "A deploymentkey for this resource already exists", portainer.ErrDeploymentkeyAlreadyExists}
}
// Add a function to call and create public key and private key
private, public, err := handler.SignatureService.GenerateDeploymentKeyPair()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create private and public key pairs", err}

View File

@ -26,16 +26,12 @@ func NewHandler(bouncer *security.RequestBouncer) *Handler {
Router: mux.NewRouter(),
}
h.Handle("/deployment_keys",
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
h.Handle("/deployment_keys",
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
h.Handle("/deployment_keys/{id}",
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyInspect))).Methods(http.MethodGet)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyInspect))).Methods(http.MethodGet)
h.Handle("/deployment_keys/{id}",
// bouncer.AuthenticatedAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
bouncer.PublicAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
return h
}

View File

@ -87,17 +87,16 @@ 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
Env []portainer.Pair
Name string
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthenticationType string
RepositoryDeploymentKey string
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
Env []portainer.Pair
}
func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) error {
@ -107,8 +106,11 @@ func (payload *composeStackFromGitRepositoryPayload) Validate(r *http.Request) e
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.RepositoryAuthenticationType == "BasicAuth" && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
return portainer.Error("Invalid repository credentials or deploymenet key. Either username & password must be specified when authentication type is BasicAuth")
}
if payload.RepositoryAuthenticationType == "DeploymentKey" && govalidator.IsNull(payload.RepositoryDeploymentKey) {
return portainer.Error("Invalid repository deploymenet key. A deployment key must be specified when authentication type is DeploymentKey")
}
if govalidator.IsNull(payload.ComposeFilePathInRepository) {
payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
@ -147,15 +149,22 @@ 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,
url: payload.RepositoryURL,
referenceName: payload.RepositoryReferenceName,
path: projectPath,
authenticationType: payload.RepositoryAuthenticationType,
deploymentKey: nil,
username: payload.RepositoryUsername,
password: payload.RepositoryPassword,
}
if payload.RepositoryDeploymentKey != "" {
deploymentKey, err := handler.DeploymentKeyService.DeploymentKeyByName(payload.Name)
if err != nil && err != portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusInternalServerError, "An error occurred retrieving deploymentkey from the database", err}
}
gitCloneParams.deploymentKey = deploymentKey.PrivateKey
}
doCleanUp := true

View File

@ -93,16 +93,16 @@ func (handler *Handler) createSwarmStackFromFileContent(w http.ResponseWriter, r
// Update struc to cater for deployment key
type swarmStackFromGitRepositoryPayload struct {
Name string
SwarmID string
Env []portainer.Pair
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
RepositoryDeploymentKey string
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
Name string
SwarmID string
Env []portainer.Pair
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthenticationType string
RepositoryDeploymentKey string
RepositoryUsername string
RepositoryPassword string
ComposeFilePathInRepository string
}
func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) error {
@ -112,18 +112,15 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err
if govalidator.IsNull(payload.SwarmID) {
return portainer.Error("Invalid Swarm ID")
}
if govalidator.IsNull(payload.RepositoryURL) || !govalidator.IsURL(payload.RepositoryURL) {
return portainer.Error("Invalid repository URL. Must correspond to a valid URL format")
}
// Need to improve validators for SSH based URL and deployment key type
/*
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 payload.RepositoryAuthenticationType == "BasicAuth" && (govalidator.IsNull(payload.RepositoryUsername) || govalidator.IsNull(payload.RepositoryPassword)) {
return portainer.Error("Invalid repository credentials or deploymenet key. Either username & password must be specified when authentication type is BasicAuth")
}
if payload.RepositoryAuthenticationType == "DeploymentKey" && govalidator.IsNull(payload.RepositoryDeploymentKey) {
return portainer.Error("Invalid repository deploymenet key. A deployment key must be specified when authentication type is DeploymentKey")
}
*/
if govalidator.IsNull(payload.ComposeFilePathInRepository) {
payload.ComposeFilePathInRepository = filesystem.ComposeFileDefaultName
}
@ -163,13 +160,22 @@ func (handler *Handler) createSwarmStackFromGitRepository(w http.ResponseWriter,
stack.ProjectPath = projectPath
gitCloneParams := &cloneRepositoryParameters{
url: payload.RepositoryURL,
referenceName: payload.RepositoryReferenceName,
path: projectPath,
authentication: payload.RepositoryAuthentication,
deploymentKey: payload.RepositoryDeploymentKey,
username: payload.RepositoryUsername,
password: payload.RepositoryPassword,
url: payload.RepositoryURL,
referenceName: payload.RepositoryReferenceName,
path: projectPath,
authenticationType: payload.RepositoryAuthenticationType,
deploymentKey: nil,
username: payload.RepositoryUsername,
password: payload.RepositoryPassword,
}
if payload.RepositoryDeploymentKey != "" {
deploymentKey, err := handler.DeploymentKeyService.DeploymentKeyByName(payload.RepositoryDeploymentKey)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "An error occurred retrieving deploymentkey from the database", err}
}
gitCloneParams.deploymentKey = deploymentKey.PrivateKey
}
doCleanUp := true

View File

@ -1,22 +1,21 @@
package stacks
type cloneRepositoryParameters struct {
url string
referenceName string
path string
authentication bool
deploymentKey string
username string
password string
url string
referenceName string
path string
authenticationType string
deploymentKey []byte
username string
password string
}
func (handler *Handler) cloneGitRepository(parameters *cloneRepositoryParameters) error {
if parameters.authentication && parameters.username != "" && parameters.password != "" {
if parameters.authenticationType != "" && 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)
if parameters.authenticationType != "" && parameters.deploymentKey != nil {
return handler.GitService.ClonePrivateRepositoryWithDeploymentKey(parameters.url, parameters.referenceName, parameters.path, parameters.deploymentKey)
}
return handler.GitService.ClonePublicRepository(parameters.url, parameters.referenceName, parameters.path)
}