1
0
Fork 0
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:
Devon Steenberg 2025-07-22 08:36:08 +12:00 committed by GitHub
parent 55cc250d2e
commit caf382b64c
20 changed files with 670 additions and 117 deletions

View file

@ -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
}

View file

@ -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))
}

View file

@ -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
}

View file

@ -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)

View file

@ -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 {

View file

@ -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))
}

View file

@ -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)
}
}

View file

@ -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)
}