Compare commits

...

9 Commits

Author SHA1 Message Date
ssbkang
a36c45822a feat(1752): run gofmt 2019-07-04 23:04:32 +12:00
ssbkang
ef800d5a7d feat(stack): Introduce SSH Key Based Deployment 2019-07-02 22:41:01 +12:00
ssbkang
5d169ad827 Fix RepositoryURL with its validator 2019-07-02 18:07:56 +12:00
ssbkang
f496776490 Introduced ClonePrivateRepositoryWithDeploymentKey and its handler 2019-07-01 23:18:04 +12:00
ssbkang
7dd9e9e365 Finalised ECDSA for Deployment Keys 2019-06-30 21:10:47 +12:00
ssbkang
adf8846c28 Add hideFields() 2019-06-28 22:45:34 +12:00
ssbkang
1732548e59 Fix validation and add error message 2019-06-27 22:19:09 +12:00
ssbkang
1a387228b0 Fix missing definitions 2019-06-27 22:00:25 +12:00
ssbkang
ba9838296e Introduce deploymentkeys 2019-06-26 23:15:48 +12:00
19 changed files with 480 additions and 50 deletions

View File

@@ -7,6 +7,7 @@ import (
"github.com/boltdb/bolt"
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/deploymentkey"
"github.com/portainer/portainer/api/bolt/dockerhub"
"github.com/portainer/portainer/api/bolt/endpoint"
"github.com/portainer/portainer/api/bolt/endpointgroup"
@@ -39,6 +40,7 @@ type Store struct {
checkForDataMigration bool
fileService portainer.FileService
RoleService *role.Service
DeploymentKeyService *deploymentkey.Service
DockerHubService *dockerhub.Service
EndpointGroupService *endpointgroup.Service
EndpointService *endpoint.Service
@@ -148,6 +150,12 @@ func (store *Store) initServices() error {
}
store.RoleService = authorizationsetService
deploymentkeyService, err := deploymentkey.NewService(store.db)
if err != nil {
return err
}
store.DeploymentKeyService = deploymentkeyService
dockerhubService, err := dockerhub.NewService(store.db)
if err != nil {
return err

View File

@@ -0,0 +1,120 @@
package deploymentkey
import (
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/bolt/internal"
"github.com/boltdb/bolt"
)
const (
// BucketName represents the name of the bucket where this service stores data.
BucketName = "deploymentkey"
)
// Service represents a service for managing deploymentkey data.
type Service struct {
db *bolt.DB
}
// NewService creates a new instance of a service.
func NewService(db *bolt.DB) (*Service, error) {
err := internal.CreateBucket(db, BucketName)
if err != nil {
return nil, err
}
return &Service{
db: db,
}, nil
}
// DeploymentKeys return all the deployment keys that are created.
func (service *Service) DeploymentKeys() ([]portainer.DeploymentKey, error) {
var deploymentkeys = make([]portainer.DeploymentKey, 0)
err := service.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var deploymentkey portainer.DeploymentKey
err := internal.UnmarshalObject(v, &deploymentkey)
if err != nil {
return err
}
deploymentkeys = append(deploymentkeys, deploymentkey)
}
return nil
})
return deploymentkeys, err
}
// DeploymentKey returns the deployment key by deployment key ID.
func (service *Service) DeploymentKey(ID portainer.DeploymentKeyID) (*portainer.DeploymentKey, error) {
var deploymentkey portainer.DeploymentKey
identifier := internal.Itob(int(ID))
err := internal.GetObject(service.db, BucketName, identifier, &deploymentkey)
if err != nil {
return nil, err
}
return &deploymentkey, nil
}
// DeploymentKeyByName returns a deploymentkey by name.
func (service *Service) DeploymentKeyByName(name string) (*portainer.DeploymentKey, error) {
var deploymentkey *portainer.DeploymentKey
err := service.db.View(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
cursor := bucket.Cursor()
for k, v := cursor.First(); k != nil; k, v = cursor.Next() {
var t portainer.DeploymentKey
err := internal.UnmarshalObject(v, &t)
if err != nil {
return err
}
if t.Name == name {
deploymentkey = &t
break
}
}
if deploymentkey == nil {
return portainer.ErrObjectNotFound
}
return nil
})
return deploymentkey, err
}
// DeleteDeploymentKey deletes a deployment key.
func (service *Service) DeleteDeploymentKey(ID portainer.DeploymentKeyID) error {
identifier := internal.Itob(int(ID))
return internal.DeleteObject(service.db, BucketName, identifier)
}
// CreateDeploymentKey creates a deployment key.
func (service *Service) CreateDeploymentKey(deploymentkey *portainer.DeploymentKey) error {
return service.db.Update(func(tx *bolt.Tx) error {
bucket := tx.Bucket([]byte(BucketName))
id, _ := bucket.NextSequence()
deploymentkey.ID = portainer.DeploymentKeyID(id)
data, err := internal.MarshalObject(deploymentkey)
if err != nil {
return err
}
return bucket.Put(internal.Itob(int(deploymentkey.ID)), data)
})
}

View File

@@ -674,6 +674,7 @@ func main() {
ResourceControlService: store.ResourceControlService,
SettingsService: store.SettingsService,
RegistryService: store.RegistryService,
DeploymentKeyService: store.DeploymentKeyService,
DockerHubService: store.DockerHubService,
StackService: store.StackService,
ScheduleService: store.ScheduleService,

View File

@@ -7,6 +7,8 @@ import (
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"golang.org/x/crypto/ssh"
"math/big"
)
@@ -135,3 +137,28 @@ func (service *ECDSAService) CreateSignature(message string) (string, error) {
return base64.RawStdEncoding.EncodeToString(signature), nil
}
// GenerateDeploymentKeyPair will create a new PEM encoded key pairs with ECDSA.
func (service *ECDSAService) GenerateDeploymentKeyPair() ([]byte, string, error) {
pubkeyCurve := elliptic.P256()
privateKey, err := ecdsa.GenerateKey(pubkeyCurve, rand.Reader)
if err != nil {
return nil, "", err
}
x509EncodedBytes, _ := x509.MarshalECPrivateKey(privateKey)
if err != nil {
return nil, "", err
}
pemEncodedPrivate := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: x509EncodedBytes})
publicKey, err := ssh.NewPublicKey(&privateKey.PublicKey)
if err != nil {
return nil, "", err
}
publicKeyAutorizedKey := ssh.MarshalAuthorizedKey(publicKey)
return pemEncodedPrivate, string(publicKeyAutorizedKey), nil
}

View File

@@ -115,3 +115,8 @@ const (
ErrWebhookAlreadyExists = Error("A webhook for this resource already exists")
ErrUnsupportedWebhookType = Error("Webhooks for this resource are not currently supported")
)
// Deploymentkey errors
const (
ErrDeploymentkeyAlreadyExists = Error("A deployment key for this resource already exists")
)

View File

@@ -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,33 @@ func cloneRepository(repositoryURL, referenceName string, destination string) er
_, err := git.PlainClone(destination, false, options)
return err
}
// ClonePrivateRepositoryWithDeploymentKey clones a private git repository using the specified URL in the specified
// destination folder. It will use the specified deployment key for SSH based authentication
func (service *Service) ClonePrivateRepositoryWithDeploymentKey(repositoryURL, referenceName string, destination string, privateKeyPem []byte) error {
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 = strings.Replace(repositoryURL, "github.com/", "github.com:", 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
}

View File

@@ -0,0 +1,58 @@
package deploymentkeys
import (
"net/http"
"github.com/asaskevich/govalidator"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
type deploymentKeyCreatePayload struct {
Name string
}
func (payload *deploymentKeyCreatePayload) Validate(r *http.Request) error {
if govalidator.IsNull(payload.Name) {
return portainer.Error("Invalid deploymentkey name")
}
return nil
}
func (handler *Handler) deploymentkeyCreate(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
var payload deploymentKeyCreatePayload
err := request.DecodeAndValidateJSONPayload(r, &payload)
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid request payload", err}
}
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}
}
if deploymentkey != nil {
return &httperror.HandlerError{http.StatusConflict, "A deploymentkey for this resource already exists", portainer.ErrDeploymentkeyAlreadyExists}
}
private, public, err := handler.SignatureService.GenerateDeploymentKeyPair()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to create private and public key pairs", err}
}
deploymentkey = &portainer.DeploymentKey{
Name: payload.Name,
PublicKey: public,
PrivateKey: private,
}
err = handler.DeploymentKeyService.CreateDeploymentKey(deploymentkey)
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to persist the deployment key inside the database", err}
}
hideFields(deploymentkey)
return response.JSON(w, deploymentkey)
}

View File

@@ -0,0 +1,25 @@
package deploymentkeys
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
// DELETE request on /api/webhook/:serviceID
func (handler *Handler) deploymentkeyDelete(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
id, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid deployment key id", err}
}
err = handler.DeploymentKeyService.DeleteDeploymentKey(portainer.DeploymentKeyID(id))
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to remove the deployment key from the database", err}
}
return response.Empty(w)
}

View File

@@ -0,0 +1,29 @@
package deploymentkeys
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
"github.com/portainer/portainer/api"
)
// GET request on /api/deployment_keys/:id
func (handler *Handler) deploymentkeyInspect(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
deploymentkeyID, err := request.RetrieveNumericRouteVariableValue(r, "id")
if err != nil {
return &httperror.HandlerError{http.StatusBadRequest, "Invalid deploymentkey identifier route variable", err}
}
deploymentkey, err := handler.DeploymentKeyService.DeploymentKey(portainer.DeploymentKeyID(deploymentkeyID))
if err == portainer.ErrObjectNotFound {
return &httperror.HandlerError{http.StatusNotFound, "Unable to find a deployment key with the specified identifier inside the database", err}
} else if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to find a deployment key with the specified identifier inside the database", err}
}
hideFields(deploymentkey)
return response.JSON(w, deploymentkey)
}

View File

@@ -0,0 +1,22 @@
package deploymentkeys
import (
"net/http"
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/response"
)
// GET request on /api/deployment_keys
func (handler *Handler) deploymentkeyList(w http.ResponseWriter, r *http.Request) *httperror.HandlerError {
deploymentkeys, err := handler.DeploymentKeyService.DeploymentKeys()
if err != nil {
return &httperror.HandlerError{http.StatusInternalServerError, "Unable to retrieve deploymentkeys from the database", err}
}
for idx := range deploymentkeys {
hideFields(&deploymentkeys[idx])
}
return response.JSON(w, deploymentkeys)
}

View File

@@ -0,0 +1,37 @@
package deploymentkeys
import (
"net/http"
"github.com/gorilla/mux"
httperror "github.com/portainer/libhttp/error"
portainer "github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/http/security"
)
// Handler is the HTTP handler used to handle deploymentkey operations.
type Handler struct {
*mux.Router
DeploymentKeyService portainer.DeploymentKeyService
SignatureService portainer.DigitalSignatureService
}
func hideFields(deploymentkey *portainer.DeploymentKey) {
deploymentkey.PrivateKey = nil
}
// NewHandler creates a handler to manage settings operations.
func NewHandler(bouncer *security.RequestBouncer) *Handler {
h := &Handler{
Router: mux.NewRouter(),
}
h.Handle("/deployment_keys",
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyCreate))).Methods(http.MethodPost)
h.Handle("/deployment_keys",
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyList))).Methods(http.MethodGet)
h.Handle("/deployment_keys/{id}",
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyInspect))).Methods(http.MethodGet)
h.Handle("/deployment_keys/{id}",
bouncer.AuthorizedAccess(httperror.LoggerHandler(h.deploymentkeyDelete))).Methods(http.MethodDelete)
return h
}

View File

@@ -9,6 +9,7 @@ import (
"github.com/portainer/portainer/api/http/handler/roles"
"github.com/portainer/portainer/api/http/handler/auth"
"github.com/portainer/portainer/api/http/handler/deploymentkeys"
"github.com/portainer/portainer/api/http/handler/dockerhub"
"github.com/portainer/portainer/api/http/handler/endpointgroups"
"github.com/portainer/portainer/api/http/handler/endpointproxy"
@@ -34,6 +35,7 @@ import (
// Handler is a collection of all the service handlers.
type Handler struct {
AuthHandler *auth.Handler
DeploymentKeyHandler *deploymentkeys.Handler
DockerHubHandler *dockerhub.Handler
EndpointGroupHandler *endpointgroups.Handler
EndpointHandler *endpoints.Handler
@@ -63,6 +65,8 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch {
case strings.HasPrefix(r.URL.Path, "/api/auth"):
http.StripPrefix("/api", h.AuthHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/deployment_keys"):
http.StripPrefix("/api", h.DeploymentKeyHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/dockerhub"):
http.StripPrefix("/api", h.DockerHubHandler).ServeHTTP(w, r)
case strings.HasPrefix(r.URL.Path, "/api/endpoint_groups"):

View File

@@ -88,14 +88,15 @@ func (handler *Handler) createComposeStackFromFileContent(w http.ResponseWriter,
}
type composeStackFromGitRepositoryPayload struct {
Name string
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
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 {
@@ -105,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
@@ -146,12 +150,21 @@ func (handler *Handler) createComposeStackFromGitRepository(w http.ResponseWrite
stack.ProjectPath = projectPath
gitCloneParams := &cloneRepositoryParameters{
url: payload.RepositoryURL,
referenceName: payload.RepositoryReferenceName,
path: projectPath,
authentication: payload.RepositoryAuthentication,
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

@@ -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,16 +91,18 @@ 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
Env []portainer.Pair
RepositoryURL string
RepositoryReferenceName string
RepositoryAuthentication bool
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 {
@@ -114,8 +115,11 @@ func (payload *swarmStackFromGitRepositoryPayload) Validate(r *http.Request) err
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
@@ -156,12 +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,
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,17 +1,21 @@
package stacks
type cloneRepositoryParameters struct {
url string
referenceName string
path string
authentication bool
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 {
if parameters.authenticationType != "" && parameters.username != "" && parameters.password != "" {
return handler.GitService.ClonePrivateRepositoryWithBasicAuth(parameters.url, parameters.referenceName, parameters.path, parameters.username, parameters.password)
}
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)
}

View File

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

View File

@@ -3,10 +3,10 @@
package proxy
import (
"github.com/Microsoft/go-winio"
"net"
"net/http"
"github.com/Microsoft/go-winio"
portainer "github.com/portainer/portainer/api"
)

View File

@@ -9,6 +9,7 @@ import (
"github.com/portainer/portainer/api/docker"
"github.com/portainer/portainer/api/http/handler"
"github.com/portainer/portainer/api/http/handler/auth"
"github.com/portainer/portainer/api/http/handler/deploymentkeys"
"github.com/portainer/portainer/api/http/handler/dockerhub"
"github.com/portainer/portainer/api/http/handler/endpointgroups"
"github.com/portainer/portainer/api/http/handler/endpointproxy"
@@ -51,6 +52,7 @@ type Server struct {
JobScheduler portainer.JobScheduler
Snapshotter portainer.Snapshotter
RoleService portainer.RoleService
DeploymentKeyService portainer.DeploymentKeyService
DockerHubService portainer.DockerHubService
EndpointService portainer.EndpointService
EndpointGroupService portainer.EndpointGroupService
@@ -180,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
@@ -222,9 +225,14 @@ func (server *Server) Start() error {
webhookHandler.EndpointService = server.EndpointService
webhookHandler.DockerClientFactory = server.DockerClientFactory
var deploymentKeyHandler = deploymentkeys.NewHandler(requestBouncer)
deploymentKeyHandler.DeploymentKeyService = server.DeploymentKeyService
deploymentKeyHandler.SignatureService = server.SignatureService
server.Handler = &handler.Handler{
RoleHandler: roleHandler,
AuthHandler: authHandler,
DeploymentKeyHandler: deploymentKeyHandler,
DockerHubHandler: dockerHubHandler,
EndpointGroupHandler: endpointGroupHandler,
EndpointHandler: endpointHandler,

View File

@@ -216,6 +216,18 @@ type (
TLSConfig TLSConfiguration `json:"TLSConfig"`
}
// DeploymentKeyID represents
DeploymentKeyID int
// DeploymentKey represents the SSH key details that will be used to
// connect to GitHub for deployments based on private key clone
DeploymentKey struct {
ID DeploymentKeyID `json:"Id"`
Name string `json:"Name"`
PublicKey string `json:"PublicKey"`
PrivateKey []byte `json:"PrivateKey"`
}
// DockerHub represents all the required information to connect and use the
// Docker Hub
DockerHub struct {
@@ -674,6 +686,15 @@ type (
GetNextIdentifier() int
}
// DeploymentKeyService represents a service for managing the DeploymentKey object
DeploymentKeyService interface {
DeploymentKey(ID DeploymentKeyID) (*DeploymentKey, error)
DeploymentKeyByName(name string) (*DeploymentKey, error)
DeploymentKeys() ([]DeploymentKey, error)
DeleteDeploymentKey(ID DeploymentKeyID) error
CreateDeploymentKey(deploymentkey *DeploymentKey) error
}
// DockerHubService represents a service for managing the DockerHub object
DockerHubService interface {
DockerHub() (*DockerHub, error)
@@ -757,6 +778,7 @@ type (
DigitalSignatureService interface {
ParseKeyPair(private, public []byte) error
GenerateKeyPair() ([]byte, []byte, error)
GenerateDeploymentKeyPair() ([]byte, string, error)
EncodedPublicKey() string
PEMHeaders() (string, string)
CreateSignature(message string) (string, error)
@@ -795,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
@@ -876,7 +899,7 @@ const (
PortainerAgentSignatureMessage = "Portainer-App"
// SupportedDockerAPIVersion is the minimum Docker API version supported by Portainer
SupportedDockerAPIVersion = "1.24"
// ExtensionServer represents the server used by Portainer to communicate with extensions
// ExtensionServer represents the server used by Portainer to communicate with extensions
ExtensionServer = "localhost"
)