mirror of
https://github.com/portainer/portainer.git
synced 2025-08-04 13:25:26 +02:00
feat(git): support bearer token auth for git [BE-11770] (#879)
This commit is contained in:
parent
55cc250d2e
commit
caf382b64c
20 changed files with 670 additions and 117 deletions
|
@ -33,13 +33,28 @@ type TestGitService struct {
|
|||
targetFilePath string
|
||||
}
|
||||
|
||||
func (g *TestGitService) CloneRepository(destination string, repositoryURL, referenceName string, username, password string, tlsSkipVerify bool) error {
|
||||
func (g *TestGitService) CloneRepository(
|
||||
destination string,
|
||||
repositoryURL,
|
||||
referenceName string,
|
||||
username,
|
||||
password string,
|
||||
authType gittypes.GitCredentialAuthType,
|
||||
tlsSkipVerify bool,
|
||||
) error {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
return createTestFile(g.targetFilePath)
|
||||
}
|
||||
|
||||
func (g *TestGitService) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
||||
func (g *TestGitService) LatestCommitID(
|
||||
repositoryURL,
|
||||
referenceName,
|
||||
username,
|
||||
password string,
|
||||
authType gittypes.GitCredentialAuthType,
|
||||
tlsSkipVerify bool,
|
||||
) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
@ -56,11 +71,26 @@ type InvalidTestGitService struct {
|
|||
targetFilePath string
|
||||
}
|
||||
|
||||
func (g *InvalidTestGitService) CloneRepository(dest, repoUrl, refName, username, password string, tlsSkipVerify bool) error {
|
||||
func (g *InvalidTestGitService) CloneRepository(
|
||||
dest,
|
||||
repoUrl,
|
||||
refName,
|
||||
username,
|
||||
password string,
|
||||
authType gittypes.GitCredentialAuthType,
|
||||
tlsSkipVerify bool,
|
||||
) error {
|
||||
return errors.New("simulate network error")
|
||||
}
|
||||
|
||||
func (g *InvalidTestGitService) LatestCommitID(repositoryURL, referenceName, username, password string, tlsSkipVerify bool) (string, error) {
|
||||
func (g *InvalidTestGitService) LatestCommitID(
|
||||
repositoryURL,
|
||||
referenceName,
|
||||
username,
|
||||
password string,
|
||||
authType gittypes.GitCredentialAuthType,
|
||||
tlsSkipVerify bool,
|
||||
) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
|
|
|
@ -37,14 +37,16 @@ type customTemplateUpdatePayload struct {
|
|||
RepositoryURL string `example:"https://github.com/openfaas/faas" validate:"required"`
|
||||
// Reference name of a Git repository hosting the Stack file
|
||||
RepositoryReferenceName string `example:"refs/heads/master"`
|
||||
// Use basic authentication to clone the Git repository
|
||||
// Use authentication to clone the Git repository
|
||||
RepositoryAuthentication bool `example:"true"`
|
||||
// Username used in basic authentication. Required when RepositoryAuthentication is true
|
||||
// and RepositoryGitCredentialID is 0
|
||||
// and RepositoryGitCredentialID is 0. Ignored if RepositoryAuthType is token
|
||||
RepositoryUsername string `example:"myGitUsername"`
|
||||
// Password used in basic authentication. Required when RepositoryAuthentication is true
|
||||
// and RepositoryGitCredentialID is 0
|
||||
// Password used in basic authentication or token used in token authentication.
|
||||
// Required when RepositoryAuthentication is true and RepositoryGitCredentialID is 0
|
||||
RepositoryPassword string `example:"myGitPassword"`
|
||||
// RepositoryAuthorizationType is the authorization type to use
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType `example:"0"`
|
||||
// GitCredentialID used to identify the bound git credential. Required when RepositoryAuthentication
|
||||
// is true and RepositoryUsername/RepositoryPassword are not provided
|
||||
RepositoryGitCredentialID int `example:"0"`
|
||||
|
@ -182,12 +184,15 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
|||
|
||||
repositoryUsername := ""
|
||||
repositoryPassword := ""
|
||||
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||
if payload.RepositoryAuthentication {
|
||||
repositoryUsername = payload.RepositoryUsername
|
||||
repositoryPassword = payload.RepositoryPassword
|
||||
repositoryAuthType = payload.RepositoryAuthorizationType
|
||||
gitConfig.Authentication = &gittypes.GitAuthentication{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +202,7 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
|||
ReferenceName: gitConfig.ReferenceName,
|
||||
Username: repositoryUsername,
|
||||
Password: repositoryPassword,
|
||||
AuthType: repositoryAuthType,
|
||||
TLSSkipVerify: gitConfig.TLSSkipVerify,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -205,7 +211,14 @@ func (handler *Handler) customTemplateUpdate(w http.ResponseWriter, r *http.Requ
|
|||
|
||||
defer cleanBackup()
|
||||
|
||||
commitHash, err := handler.GitService.LatestCommitID(gitConfig.URL, gitConfig.ReferenceName, repositoryUsername, repositoryPassword, gitConfig.TLSSkipVerify)
|
||||
commitHash, err := handler.GitService.LatestCommitID(
|
||||
gitConfig.URL,
|
||||
gitConfig.ReferenceName,
|
||||
repositoryUsername,
|
||||
repositoryPassword,
|
||||
repositoryAuthType,
|
||||
gitConfig.TLSSkipVerify,
|
||||
)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable get latest commit id", fmt.Errorf("failed to fetch latest commit id of the template %v: %w", customTemplate.ID, err))
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ type edgeStackFromGitRepositoryPayload struct {
|
|||
RepositoryUsername string `example:"myGitUsername"`
|
||||
// Password used in basic authentication. Required when RepositoryAuthentication is true.
|
||||
RepositoryPassword string `example:"myGitPassword"`
|
||||
// RepositoryAuthorizationType is the authorization type to use
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType `example:"0"`
|
||||
// Path to the Stack file inside the Git repository
|
||||
FilePathInRepository string `example:"docker-compose.yml" default:"docker-compose.yml"`
|
||||
// List of identifiers of EdgeGroups
|
||||
|
@ -125,8 +127,9 @@ func (handler *Handler) createEdgeStackFromGitRepository(r *http.Request, tx dat
|
|||
|
||||
if payload.RepositoryAuthentication {
|
||||
repoConfig.Authentication = &gittypes.GitAuthentication{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: payload.RepositoryPassword,
|
||||
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -145,12 +148,22 @@ func (handler *Handler) storeManifestFromGitRepository(tx dataservices.DataStore
|
|||
projectPath = handler.FileService.GetEdgeStackProjectPath(stackFolder)
|
||||
repositoryUsername := ""
|
||||
repositoryPassword := ""
|
||||
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||
if repositoryConfig.Authentication != nil && repositoryConfig.Authentication.Password != "" {
|
||||
repositoryUsername = repositoryConfig.Authentication.Username
|
||||
repositoryPassword = repositoryConfig.Authentication.Password
|
||||
repositoryAuthType = repositoryConfig.Authentication.AuthorizationType
|
||||
}
|
||||
|
||||
if err := handler.GitService.CloneRepository(projectPath, repositoryConfig.URL, repositoryConfig.ReferenceName, repositoryUsername, repositoryPassword, repositoryConfig.TLSSkipVerify); err != nil {
|
||||
if err := handler.GitService.CloneRepository(
|
||||
projectPath,
|
||||
repositoryConfig.URL,
|
||||
repositoryConfig.ReferenceName,
|
||||
repositoryUsername,
|
||||
repositoryPassword,
|
||||
repositoryAuthType,
|
||||
repositoryConfig.TLSSkipVerify,
|
||||
); err != nil {
|
||||
return "", "", "", err
|
||||
}
|
||||
|
||||
|
|
|
@ -17,10 +17,11 @@ type fileResponse struct {
|
|||
}
|
||||
|
||||
type repositoryFilePreviewPayload struct {
|
||||
Repository string `json:"repository" example:"https://github.com/openfaas/faas" validate:"required"`
|
||||
Reference string `json:"reference" example:"refs/heads/master"`
|
||||
Username string `json:"username" example:"myGitUsername"`
|
||||
Password string `json:"password" example:"myGitPassword"`
|
||||
Repository string `json:"repository" example:"https://github.com/openfaas/faas" validate:"required"`
|
||||
Reference string `json:"reference" example:"refs/heads/master"`
|
||||
Username string `json:"username" example:"myGitUsername"`
|
||||
Password string `json:"password" example:"myGitPassword"`
|
||||
AuthorizationType gittypes.GitCredentialAuthType `json:"authorizationType"`
|
||||
// Path to file whose content will be read
|
||||
TargetFile string `json:"targetFile" example:"docker-compose.yml"`
|
||||
// TLSSkipVerify skips SSL verification when cloning the Git repository
|
||||
|
@ -68,7 +69,15 @@ func (handler *Handler) gitOperationRepoFilePreview(w http.ResponseWriter, r *ht
|
|||
return httperror.InternalServerError("Unable to create temporary folder", err)
|
||||
}
|
||||
|
||||
err = handler.gitService.CloneRepository(projectPath, payload.Repository, payload.Reference, payload.Username, payload.Password, payload.TLSSkipVerify)
|
||||
err = handler.gitService.CloneRepository(
|
||||
projectPath,
|
||||
payload.Repository,
|
||||
payload.Reference,
|
||||
payload.Username,
|
||||
payload.Password,
|
||||
payload.AuthorizationType,
|
||||
payload.TLSSkipVerify,
|
||||
)
|
||||
if err != nil {
|
||||
if errors.Is(err, gittypes.ErrAuthenticationFailure) {
|
||||
return httperror.BadRequest("Invalid git credential", err)
|
||||
|
|
|
@ -19,14 +19,15 @@ import (
|
|||
)
|
||||
|
||||
type stackGitUpdatePayload struct {
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
TLSSkipVerify bool
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||
TLSSkipVerify bool
|
||||
}
|
||||
|
||||
func (payload *stackGitUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -151,11 +152,19 @@ func (handler *Handler) stackUpdateGit(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
|
||||
stack.GitConfig.Authentication = &gittypes.GitAuthentication{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: password,
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: password,
|
||||
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||
}
|
||||
|
||||
if _, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify); err != nil {
|
||||
if _, err := handler.GitService.LatestCommitID(
|
||||
stack.GitConfig.URL,
|
||||
stack.GitConfig.ReferenceName,
|
||||
stack.GitConfig.Authentication.Username,
|
||||
stack.GitConfig.Authentication.Password,
|
||||
stack.GitConfig.Authentication.AuthorizationType,
|
||||
stack.GitConfig.TLSSkipVerify,
|
||||
); err != nil {
|
||||
return httperror.InternalServerError("Unable to fetch git repository", err)
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/git"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
httperrors "github.com/portainer/portainer/api/http/errors"
|
||||
"github.com/portainer/portainer/api/http/security"
|
||||
k "github.com/portainer/portainer/api/kubernetes"
|
||||
|
@ -19,12 +20,13 @@ import (
|
|||
)
|
||||
|
||||
type stackGitRedployPayload struct {
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||
Env []portainer.Pair
|
||||
Prune bool
|
||||
// Force a pulling to current image with the original tag though the image is already the latest
|
||||
PullImage bool `example:"false"`
|
||||
|
||||
|
@ -135,13 +137,16 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
|||
|
||||
repositoryUsername := ""
|
||||
repositoryPassword := ""
|
||||
repositoryAuthType := gittypes.GitCredentialAuthType_Basic
|
||||
if payload.RepositoryAuthentication {
|
||||
repositoryPassword = payload.RepositoryPassword
|
||||
repositoryAuthType = payload.RepositoryAuthorizationType
|
||||
|
||||
// When the existing stack is using the custom username/password and the password is not updated,
|
||||
// the stack should keep using the saved username/password
|
||||
if repositoryPassword == "" && stack.GitConfig != nil && stack.GitConfig.Authentication != nil {
|
||||
repositoryPassword = stack.GitConfig.Authentication.Password
|
||||
repositoryAuthType = stack.GitConfig.Authentication.AuthorizationType
|
||||
}
|
||||
repositoryUsername = payload.RepositoryUsername
|
||||
}
|
||||
|
@ -152,6 +157,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
|||
ReferenceName: stack.GitConfig.ReferenceName,
|
||||
Username: repositoryUsername,
|
||||
Password: repositoryPassword,
|
||||
AuthType: repositoryAuthType,
|
||||
TLSSkipVerify: stack.GitConfig.TLSSkipVerify,
|
||||
}
|
||||
|
||||
|
@ -166,7 +172,7 @@ func (handler *Handler) stackGitRedeploy(w http.ResponseWriter, r *http.Request)
|
|||
return err
|
||||
}
|
||||
|
||||
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, stack.GitConfig.TLSSkipVerify)
|
||||
newHash, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, repositoryUsername, repositoryPassword, repositoryAuthType, stack.GitConfig.TLSSkipVerify)
|
||||
if err != nil {
|
||||
return httperror.InternalServerError("Unable get latest commit id", errors.WithMessagef(err, "failed to fetch latest commit id of the stack %v", stack.ID))
|
||||
}
|
||||
|
|
|
@ -27,12 +27,13 @@ type kubernetesFileStackUpdatePayload struct {
|
|||
}
|
||||
|
||||
type kubernetesGitStackUpdatePayload struct {
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
TLSSkipVerify bool
|
||||
RepositoryReferenceName string
|
||||
RepositoryAuthentication bool
|
||||
RepositoryUsername string
|
||||
RepositoryPassword string
|
||||
RepositoryAuthorizationType gittypes.GitCredentialAuthType
|
||||
AutoUpdate *portainer.AutoUpdateSettings
|
||||
TLSSkipVerify bool
|
||||
}
|
||||
|
||||
func (payload *kubernetesFileStackUpdatePayload) Validate(r *http.Request) error {
|
||||
|
@ -76,11 +77,19 @@ func (handler *Handler) updateKubernetesStack(r *http.Request, stack *portainer.
|
|||
}
|
||||
|
||||
stack.GitConfig.Authentication = &gittypes.GitAuthentication{
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: password,
|
||||
Username: payload.RepositoryUsername,
|
||||
Password: password,
|
||||
AuthorizationType: payload.RepositoryAuthorizationType,
|
||||
}
|
||||
|
||||
if _, err := handler.GitService.LatestCommitID(stack.GitConfig.URL, stack.GitConfig.ReferenceName, stack.GitConfig.Authentication.Username, stack.GitConfig.Authentication.Password, stack.GitConfig.TLSSkipVerify); err != nil {
|
||||
if _, err := handler.GitService.LatestCommitID(
|
||||
stack.GitConfig.URL,
|
||||
stack.GitConfig.ReferenceName,
|
||||
stack.GitConfig.Authentication.Username,
|
||||
stack.GitConfig.Authentication.Password,
|
||||
stack.GitConfig.Authentication.AuthorizationType,
|
||||
stack.GitConfig.TLSSkipVerify,
|
||||
); err != nil {
|
||||
return httperror.InternalServerError("Unable to fetch git repository", err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"slices"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
gittypes "github.com/portainer/portainer/api/git/types"
|
||||
httperror "github.com/portainer/portainer/pkg/libhttp/error"
|
||||
"github.com/portainer/portainer/pkg/libhttp/request"
|
||||
"github.com/portainer/portainer/pkg/libhttp/response"
|
||||
|
@ -71,7 +72,15 @@ func (handler *Handler) templateFile(w http.ResponseWriter, r *http.Request) *ht
|
|||
|
||||
defer handler.cleanUp(projectPath)
|
||||
|
||||
if err := handler.GitService.CloneRepository(projectPath, template.Repository.URL, "", "", "", false); err != nil {
|
||||
if err := handler.GitService.CloneRepository(
|
||||
projectPath,
|
||||
template.Repository.URL,
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
gittypes.GitCredentialAuthType_Basic,
|
||||
false,
|
||||
); err != nil {
|
||||
return httperror.InternalServerError("Unable to clone git repository", err)
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue