feat(api): TLS endpoint creation and init overhaul (#1173)
parent
87825f7ebb
commit
8d4807c9e7
|
@ -2,7 +2,7 @@ package bolt
|
||||||
|
|
||||||
import "github.com/portainer/portainer"
|
import "github.com/portainer/portainer"
|
||||||
|
|
||||||
func (m *Migrator) updateSettingsToVersion3() error {
|
func (m *Migrator) updateSettingsToDBVersion3() error {
|
||||||
legacySettings, err := m.SettingsService.Settings()
|
legacySettings, err := m.SettingsService.Settings()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package bolt
|
||||||
|
|
||||||
|
import "github.com/portainer/portainer"
|
||||||
|
|
||||||
|
func (m *Migrator) updateEndpointsToDBVersion4() error {
|
||||||
|
legacyEndpoints, err := m.EndpointService.Endpoints()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, endpoint := range legacyEndpoints {
|
||||||
|
endpoint.TLSConfig = portainer.TLSConfiguration{}
|
||||||
|
if endpoint.TLS {
|
||||||
|
endpoint.TLSConfig.TLS = true
|
||||||
|
endpoint.TLSConfig.TLSSkipVerify = false
|
||||||
|
endpoint.TLSConfig.TLSCACertPath = endpoint.TLSCACertPath
|
||||||
|
endpoint.TLSConfig.TLSCertPath = endpoint.TLSCertPath
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = endpoint.TLSKeyPath
|
||||||
|
}
|
||||||
|
err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ func NewMigrator(store *Store, version int) *Migrator {
|
||||||
func (m *Migrator) Migrate() error {
|
func (m *Migrator) Migrate() error {
|
||||||
|
|
||||||
// Portainer < 1.12
|
// Portainer < 1.12
|
||||||
if m.CurrentDBVersion == 0 {
|
if m.CurrentDBVersion < 1 {
|
||||||
err := m.updateAdminUserToDBVersion1()
|
err := m.updateAdminUserToDBVersion1()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -38,7 +38,7 @@ func (m *Migrator) Migrate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Portainer 1.12.x
|
// Portainer 1.12.x
|
||||||
if m.CurrentDBVersion == 1 {
|
if m.CurrentDBVersion < 2 {
|
||||||
err := m.updateResourceControlsToDBVersion2()
|
err := m.updateResourceControlsToDBVersion2()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -50,8 +50,16 @@ func (m *Migrator) Migrate() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Portainer 1.13.x
|
// Portainer 1.13.x
|
||||||
if m.CurrentDBVersion == 2 {
|
if m.CurrentDBVersion < 3 {
|
||||||
err := m.updateSettingsToVersion3()
|
err := m.updateSettingsToDBVersion3()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Portainer 1.14.0
|
||||||
|
if m.CurrentDBVersion < 4 {
|
||||||
|
err := m.updateEndpointsToDBVersion4()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -193,10 +193,13 @@ func main() {
|
||||||
endpoint := &portainer.Endpoint{
|
endpoint := &portainer.Endpoint{
|
||||||
Name: "primary",
|
Name: "primary",
|
||||||
URL: *flags.Endpoint,
|
URL: *flags.Endpoint,
|
||||||
|
TLSConfig: portainer.TLSConfiguration{
|
||||||
TLS: *flags.TLSVerify,
|
TLS: *flags.TLSVerify,
|
||||||
|
TLSSkipVerify: false,
|
||||||
TLSCACertPath: *flags.TLSCacert,
|
TLSCACertPath: *flags.TLSCacert,
|
||||||
TLSCertPath: *flags.TLSCert,
|
TLSCertPath: *flags.TLSCert,
|
||||||
TLSKeyPath: *flags.TLSKey,
|
TLSKeyPath: *flags.TLSKey,
|
||||||
|
},
|
||||||
AuthorizedUsers: []portainer.UserID{},
|
AuthorizedUsers: []portainer.UserID{},
|
||||||
AuthorizedTeams: []portainer.TeamID{},
|
AuthorizedTeams: []portainer.TeamID{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,16 @@ type (
|
||||||
endpointsToUpdate []*portainer.Endpoint
|
endpointsToUpdate []*portainer.Endpoint
|
||||||
endpointsToDelete []*portainer.Endpoint
|
endpointsToDelete []*portainer.Endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fileEndpoint struct {
|
||||||
|
Name string `json:"Name"`
|
||||||
|
URL string `json:"URL"`
|
||||||
|
TLS bool `json:"TLS,omitempty"`
|
||||||
|
TLSSkipVerify bool `json:"TLSSkipVerify,omitempty"`
|
||||||
|
TLSCACert string `json:"TLSCACert,omitempty"`
|
||||||
|
TLSCert string `json:"TLSCert,omitempty"`
|
||||||
|
TLSKey string `json:"TLSKey,omitempty"`
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -55,6 +65,28 @@ func isValidEndpoint(endpoint *portainer.Endpoint) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertFileEndpoints(fileEndpoints []fileEndpoint) []portainer.Endpoint {
|
||||||
|
convertedEndpoints := make([]portainer.Endpoint, 0)
|
||||||
|
|
||||||
|
for _, e := range fileEndpoints {
|
||||||
|
endpoint := portainer.Endpoint{
|
||||||
|
Name: e.Name,
|
||||||
|
URL: e.URL,
|
||||||
|
TLSConfig: portainer.TLSConfiguration{},
|
||||||
|
}
|
||||||
|
if e.TLS {
|
||||||
|
endpoint.TLSConfig.TLS = true
|
||||||
|
endpoint.TLSConfig.TLSSkipVerify = e.TLSSkipVerify
|
||||||
|
endpoint.TLSConfig.TLSCACertPath = e.TLSCACert
|
||||||
|
endpoint.TLSConfig.TLSCertPath = e.TLSCert
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = e.TLSKey
|
||||||
|
}
|
||||||
|
convertedEndpoints = append(convertedEndpoints, endpoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
return convertedEndpoints
|
||||||
|
}
|
||||||
|
|
||||||
func endpointExists(endpoint *portainer.Endpoint, endpoints []portainer.Endpoint) int {
|
func endpointExists(endpoint *portainer.Endpoint, endpoints []portainer.Endpoint) int {
|
||||||
for idx, v := range endpoints {
|
for idx, v := range endpoints {
|
||||||
if endpoint.Name == v.Name && isValidEndpoint(&v) {
|
if endpoint.Name == v.Name && isValidEndpoint(&v) {
|
||||||
|
@ -66,22 +98,25 @@ func endpointExists(endpoint *portainer.Endpoint, endpoints []portainer.Endpoint
|
||||||
|
|
||||||
func mergeEndpointIfRequired(original, updated *portainer.Endpoint) *portainer.Endpoint {
|
func mergeEndpointIfRequired(original, updated *portainer.Endpoint) *portainer.Endpoint {
|
||||||
var endpoint *portainer.Endpoint
|
var endpoint *portainer.Endpoint
|
||||||
if original.URL != updated.URL || original.TLS != updated.TLS ||
|
if original.URL != updated.URL || original.TLSConfig.TLS != updated.TLSConfig.TLS ||
|
||||||
(updated.TLS && original.TLSCACertPath != updated.TLSCACertPath) ||
|
(updated.TLSConfig.TLS && original.TLSConfig.TLSSkipVerify != updated.TLSConfig.TLSSkipVerify) ||
|
||||||
(updated.TLS && original.TLSCertPath != updated.TLSCertPath) ||
|
(updated.TLSConfig.TLS && original.TLSConfig.TLSCACertPath != updated.TLSConfig.TLSCACertPath) ||
|
||||||
(updated.TLS && original.TLSKeyPath != updated.TLSKeyPath) {
|
(updated.TLSConfig.TLS && original.TLSConfig.TLSCertPath != updated.TLSConfig.TLSCertPath) ||
|
||||||
|
(updated.TLSConfig.TLS && original.TLSConfig.TLSKeyPath != updated.TLSConfig.TLSKeyPath) {
|
||||||
endpoint = original
|
endpoint = original
|
||||||
endpoint.URL = updated.URL
|
endpoint.URL = updated.URL
|
||||||
if updated.TLS {
|
if updated.TLSConfig.TLS {
|
||||||
endpoint.TLS = true
|
endpoint.TLSConfig.TLS = true
|
||||||
endpoint.TLSCACertPath = updated.TLSCACertPath
|
endpoint.TLSConfig.TLSSkipVerify = updated.TLSConfig.TLSSkipVerify
|
||||||
endpoint.TLSCertPath = updated.TLSCertPath
|
endpoint.TLSConfig.TLSCACertPath = updated.TLSConfig.TLSCACertPath
|
||||||
endpoint.TLSKeyPath = updated.TLSKeyPath
|
endpoint.TLSConfig.TLSCertPath = updated.TLSConfig.TLSCertPath
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = updated.TLSConfig.TLSKeyPath
|
||||||
} else {
|
} else {
|
||||||
endpoint.TLS = false
|
endpoint.TLSConfig.TLS = false
|
||||||
endpoint.TLSCACertPath = ""
|
endpoint.TLSConfig.TLSSkipVerify = false
|
||||||
endpoint.TLSCertPath = ""
|
endpoint.TLSConfig.TLSCACertPath = ""
|
||||||
endpoint.TLSKeyPath = ""
|
endpoint.TLSConfig.TLSCertPath = ""
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return endpoint
|
return endpoint
|
||||||
|
@ -141,7 +176,7 @@ func (job endpointSyncJob) Sync() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var fileEndpoints []portainer.Endpoint
|
var fileEndpoints []fileEndpoint
|
||||||
err = json.Unmarshal(data, &fileEndpoints)
|
err = json.Unmarshal(data, &fileEndpoints)
|
||||||
if endpointSyncError(err, job.logger) {
|
if endpointSyncError(err, job.logger) {
|
||||||
return err
|
return err
|
||||||
|
@ -156,7 +191,9 @@ func (job endpointSyncJob) Sync() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sync := job.prepareSyncData(storedEndpoints, fileEndpoints)
|
convertedFileEndpoints := convertFileEndpoints(fileEndpoints)
|
||||||
|
|
||||||
|
sync := job.prepareSyncData(storedEndpoints, convertedFileEndpoints)
|
||||||
if sync.requireSync() {
|
if sync.requireSync() {
|
||||||
err = job.endpointService.Synchronize(sync.endpointsToCreate, sync.endpointsToUpdate, sync.endpointsToDelete)
|
err = job.endpointService.Synchronize(sync.endpointsToCreate, sync.endpointsToUpdate, sync.endpointsToDelete)
|
||||||
if endpointSyncError(err, job.logger) {
|
if endpointSyncError(err, job.logger) {
|
||||||
|
|
|
@ -4,31 +4,38 @@ import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
|
||||||
|
"github.com/portainer/portainer"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CreateTLSConfiguration initializes a tls.Config using a CA certificate, a certificate and a key
|
// CreateTLSConfiguration initializes a tls.Config using a CA certificate, a certificate and a key
|
||||||
func CreateTLSConfiguration(caCertPath, certPath, keyPath string, skipTLSVerify bool) (*tls.Config, error) {
|
func CreateTLSConfiguration(config *portainer.TLSConfiguration) (*tls.Config, error) {
|
||||||
|
TLSConfig := &tls.Config{}
|
||||||
|
|
||||||
config := &tls.Config{}
|
if config.TLS {
|
||||||
|
if config.TLSCertPath != "" && config.TLSKeyPath != "" {
|
||||||
if certPath != "" && keyPath != "" {
|
cert, err := tls.LoadX509KeyPair(config.TLSCertPath, config.TLSKeyPath)
|
||||||
cert, err := tls.LoadX509KeyPair(certPath, keyPath)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
config.Certificates = []tls.Certificate{cert}
|
|
||||||
|
TLSConfig.Certificates = []tls.Certificate{cert}
|
||||||
}
|
}
|
||||||
|
|
||||||
if caCertPath != "" {
|
if !config.TLSSkipVerify {
|
||||||
caCert, err := ioutil.ReadFile(caCertPath)
|
caCert, err := ioutil.ReadFile(config.TLSCACertPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
caCertPool := x509.NewCertPool()
|
caCertPool := x509.NewCertPool()
|
||||||
caCertPool.AppendCertsFromPEM(caCert)
|
caCertPool.AppendCertsFromPEM(caCert)
|
||||||
config.RootCAs = caCertPool
|
|
||||||
|
TLSConfig.RootCAs = caCertPool
|
||||||
}
|
}
|
||||||
|
|
||||||
config.InsecureSkipVerify = skipTLSVerify
|
TLSConfig.InsecureSkipVerify = config.TLSSkipVerify
|
||||||
return config, nil
|
}
|
||||||
|
|
||||||
|
return TLSConfig, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,7 @@ func (service *Service) GetPathForTLSFile(folder string, fileType portainer.TLSF
|
||||||
return path.Join(service.fileStorePath, TLSStorePath, folder, fileName), nil
|
return path.Join(service.fileStorePath, TLSStorePath, folder, fileName), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteTLSFiles deletes a folder containing the TLS files for an endpoint.
|
// DeleteTLSFiles deletes a folder in the TLS store path.
|
||||||
func (service *Service) DeleteTLSFiles(folder string) error {
|
func (service *Service) DeleteTLSFiles(folder string) error {
|
||||||
storePath := path.Join(service.fileStorePath, TLSStorePath, folder)
|
storePath := path.Join(service.fileStorePath, TLSStorePath, folder)
|
||||||
err := os.RemoveAll(storePath)
|
err := os.RemoveAll(storePath)
|
||||||
|
@ -105,6 +105,29 @@ func (service *Service) DeleteTLSFiles(folder string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteTLSFile deletes a specific TLS file from a folder.
|
||||||
|
func (service *Service) DeleteTLSFile(folder string, fileType portainer.TLSFileType) error {
|
||||||
|
var fileName string
|
||||||
|
switch fileType {
|
||||||
|
case portainer.TLSFileCA:
|
||||||
|
fileName = TLSCACertFile
|
||||||
|
case portainer.TLSFileCert:
|
||||||
|
fileName = TLSCertFile
|
||||||
|
case portainer.TLSFileKey:
|
||||||
|
fileName = TLSKeyFile
|
||||||
|
default:
|
||||||
|
return portainer.ErrUndefinedTLSFileType
|
||||||
|
}
|
||||||
|
|
||||||
|
filePath := path.Join(service.fileStorePath, TLSStorePath, folder, fileName)
|
||||||
|
|
||||||
|
err := os.Remove(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// createDirectoryInStoreIfNotExist creates a new directory in the file store if it doesn't exists on the file system.
|
// createDirectoryInStoreIfNotExist creates a new directory in the file store if it doesn't exists on the file system.
|
||||||
func (service *Service) createDirectoryInStoreIfNotExist(name string) error {
|
func (service *Service) createDirectoryInStoreIfNotExist(name string) error {
|
||||||
path := path.Join(service.fileStorePath, name)
|
path := path.Join(service.fileStorePath, name)
|
||||||
|
|
|
@ -61,6 +61,8 @@ type (
|
||||||
URL string `valid:"required"`
|
URL string `valid:"required"`
|
||||||
PublicURL string `valid:"-"`
|
PublicURL string `valid:"-"`
|
||||||
TLS bool
|
TLS bool
|
||||||
|
TLSSkipVerify bool
|
||||||
|
TLSSkipClientVerify bool
|
||||||
}
|
}
|
||||||
|
|
||||||
postEndpointsResponse struct {
|
postEndpointsResponse struct {
|
||||||
|
@ -77,6 +79,8 @@ type (
|
||||||
URL string `valid:"-"`
|
URL string `valid:"-"`
|
||||||
PublicURL string `valid:"-"`
|
PublicURL string `valid:"-"`
|
||||||
TLS bool `valid:"-"`
|
TLS bool `valid:"-"`
|
||||||
|
TLSSkipVerify bool `valid:"-"`
|
||||||
|
TLSSkipClientVerify bool `valid:"-"`
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -126,7 +130,10 @@ func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *ht
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
PublicURL: req.PublicURL,
|
PublicURL: req.PublicURL,
|
||||||
|
TLSConfig: portainer.TLSConfiguration{
|
||||||
TLS: req.TLS,
|
TLS: req.TLS,
|
||||||
|
TLSSkipVerify: req.TLSSkipVerify,
|
||||||
|
},
|
||||||
AuthorizedUsers: []portainer.UserID{},
|
AuthorizedUsers: []portainer.UserID{},
|
||||||
AuthorizedTeams: []portainer.TeamID{},
|
AuthorizedTeams: []portainer.TeamID{},
|
||||||
}
|
}
|
||||||
|
@ -139,12 +146,19 @@ func (handler *EndpointHandler) handlePostEndpoints(w http.ResponseWriter, r *ht
|
||||||
|
|
||||||
if req.TLS {
|
if req.TLS {
|
||||||
folder := strconv.Itoa(int(endpoint.ID))
|
folder := strconv.Itoa(int(endpoint.ID))
|
||||||
|
|
||||||
|
if !req.TLSSkipVerify {
|
||||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCA)
|
caCertPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCA)
|
||||||
endpoint.TLSCACertPath = caCertPath
|
endpoint.TLSConfig.TLSCACertPath = caCertPath
|
||||||
|
}
|
||||||
|
|
||||||
|
if !req.TLSSkipClientVerify {
|
||||||
certPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCert)
|
certPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCert)
|
||||||
endpoint.TLSCertPath = certPath
|
endpoint.TLSConfig.TLSCertPath = certPath
|
||||||
keyPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileKey)
|
keyPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileKey)
|
||||||
endpoint.TLSKeyPath = keyPath
|
endpoint.TLSConfig.TLSKeyPath = keyPath
|
||||||
|
}
|
||||||
|
|
||||||
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
|
err = handler.EndpointService.UpdateEndpoint(endpoint.ID, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
|
@ -284,18 +298,33 @@ func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http
|
||||||
|
|
||||||
folder := strconv.Itoa(int(endpoint.ID))
|
folder := strconv.Itoa(int(endpoint.ID))
|
||||||
if req.TLS {
|
if req.TLS {
|
||||||
endpoint.TLS = true
|
endpoint.TLSConfig.TLS = true
|
||||||
|
endpoint.TLSConfig.TLSSkipVerify = req.TLSSkipVerify
|
||||||
|
if !req.TLSSkipVerify {
|
||||||
caCertPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCA)
|
caCertPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCA)
|
||||||
endpoint.TLSCACertPath = caCertPath
|
endpoint.TLSConfig.TLSCACertPath = caCertPath
|
||||||
certPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCert)
|
|
||||||
endpoint.TLSCertPath = certPath
|
|
||||||
keyPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileKey)
|
|
||||||
endpoint.TLSKeyPath = keyPath
|
|
||||||
} else {
|
} else {
|
||||||
endpoint.TLS = false
|
endpoint.TLSConfig.TLSCACertPath = ""
|
||||||
endpoint.TLSCACertPath = ""
|
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCA)
|
||||||
endpoint.TLSCertPath = ""
|
}
|
||||||
endpoint.TLSKeyPath = ""
|
|
||||||
|
if !req.TLSSkipClientVerify {
|
||||||
|
certPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileCert)
|
||||||
|
endpoint.TLSConfig.TLSCertPath = certPath
|
||||||
|
keyPath, _ := handler.FileService.GetPathForTLSFile(folder, portainer.TLSFileKey)
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = keyPath
|
||||||
|
} else {
|
||||||
|
endpoint.TLSConfig.TLSCertPath = ""
|
||||||
|
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileCert)
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = ""
|
||||||
|
handler.FileService.DeleteTLSFile(folder, portainer.TLSFileKey)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
endpoint.TLSConfig.TLS = false
|
||||||
|
endpoint.TLSConfig.TLSSkipVerify = true
|
||||||
|
endpoint.TLSConfig.TLSCACertPath = ""
|
||||||
|
endpoint.TLSConfig.TLSCertPath = ""
|
||||||
|
endpoint.TLSConfig.TLSKeyPath = ""
|
||||||
err = handler.FileService.DeleteTLSFiles(folder)
|
err = handler.FileService.DeleteTLSFiles(folder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
|
@ -350,7 +379,7 @@ func (handler *EndpointHandler) handleDeleteEndpoint(w http.ResponseWriter, r *h
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpoint.TLS {
|
if endpoint.TLSConfig.TLS {
|
||||||
err = handler.FileService.DeleteTLSFiles(id)
|
err = handler.FileService.DeleteTLSFiles(id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
httperror.WriteErrorResponse(w, err, http.StatusInternalServerError, handler.Logger)
|
||||||
|
|
|
@ -71,8 +71,8 @@ func (handler *WebSocketHandler) webSocketDockerExec(ws *websocket.Conn) {
|
||||||
|
|
||||||
// Should not be managed here
|
// Should not be managed here
|
||||||
var tlsConfig *tls.Config
|
var tlsConfig *tls.Config
|
||||||
if endpoint.TLS {
|
if endpoint.TLSConfig.TLS {
|
||||||
tlsConfig, err = crypto.CreateTLSConfiguration(endpoint.TLSCACertPath, endpoint.TLSCertPath, endpoint.TLSKeyPath, false)
|
tlsConfig, err = crypto.CreateTLSConfiguration(&endpoint.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Unable to create TLS configuration: %s", err)
|
log.Fatalf("Unable to create TLS configuration: %s", err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -24,7 +24,7 @@ func (factory *proxyFactory) newHTTPProxy(u *url.URL) http.Handler {
|
||||||
func (factory *proxyFactory) newHTTPSProxy(u *url.URL, endpoint *portainer.Endpoint) (http.Handler, error) {
|
func (factory *proxyFactory) newHTTPSProxy(u *url.URL, endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||||
u.Scheme = "https"
|
u.Scheme = "https"
|
||||||
proxy := factory.createReverseProxy(u)
|
proxy := factory.createReverseProxy(u)
|
||||||
config, err := crypto.CreateTLSConfiguration(endpoint.TLSCACertPath, endpoint.TLSCertPath, endpoint.TLSKeyPath, false)
|
config, err := crypto.CreateTLSConfiguration(&endpoint.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (ht
|
||||||
}
|
}
|
||||||
|
|
||||||
if endpointURL.Scheme == "tcp" {
|
if endpointURL.Scheme == "tcp" {
|
||||||
if endpoint.TLS {
|
if endpoint.TLSConfig.TLS {
|
||||||
proxy, err = manager.proxyFactory.newHTTPSProxy(endpointURL, endpoint)
|
proxy, err = manager.proxyFactory.newHTTPSProxy(endpointURL, endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -52,7 +52,7 @@ func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearc
|
||||||
func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) {
|
func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) {
|
||||||
|
|
||||||
if settings.TLSConfig.TLS || settings.StartTLS {
|
if settings.TLSConfig.TLS || settings.StartTLS {
|
||||||
config, err := crypto.CreateTLSConfiguration(settings.TLSConfig.TLSCACertPath, "", "", settings.TLSConfig.TLSSkipVerify)
|
config, err := crypto.CreateTLSConfiguration(&settings.TLSConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -159,12 +159,16 @@ type (
|
||||||
Name string `json:"Name"`
|
Name string `json:"Name"`
|
||||||
URL string `json:"URL"`
|
URL string `json:"URL"`
|
||||||
PublicURL string `json:"PublicURL"`
|
PublicURL string `json:"PublicURL"`
|
||||||
TLS bool `json:"TLS"`
|
TLSConfig TLSConfiguration `json:"TLSConfig"`
|
||||||
|
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
||||||
|
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
||||||
|
|
||||||
|
// Deprecated fields
|
||||||
|
// Deprecated in DBVersion == 4
|
||||||
|
TLS bool `json:"TLS,omitempty"`
|
||||||
TLSCACertPath string `json:"TLSCACert,omitempty"`
|
TLSCACertPath string `json:"TLSCACert,omitempty"`
|
||||||
TLSCertPath string `json:"TLSCert,omitempty"`
|
TLSCertPath string `json:"TLSCert,omitempty"`
|
||||||
TLSKeyPath string `json:"TLSKey,omitempty"`
|
TLSKeyPath string `json:"TLSKey,omitempty"`
|
||||||
AuthorizedUsers []UserID `json:"AuthorizedUsers"`
|
|
||||||
AuthorizedTeams []TeamID `json:"AuthorizedTeams"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceControlID represents a resource control identifier.
|
// ResourceControlID represents a resource control identifier.
|
||||||
|
@ -177,15 +181,13 @@ type (
|
||||||
SubResourceIDs []string `json:"SubResourceIds"`
|
SubResourceIDs []string `json:"SubResourceIds"`
|
||||||
Type ResourceControlType `json:"Type"`
|
Type ResourceControlType `json:"Type"`
|
||||||
AdministratorsOnly bool `json:"AdministratorsOnly"`
|
AdministratorsOnly bool `json:"AdministratorsOnly"`
|
||||||
|
|
||||||
UserAccesses []UserResourceAccess `json:"UserAccesses"`
|
UserAccesses []UserResourceAccess `json:"UserAccesses"`
|
||||||
TeamAccesses []TeamResourceAccess `json:"TeamAccesses"`
|
TeamAccesses []TeamResourceAccess `json:"TeamAccesses"`
|
||||||
|
|
||||||
// Deprecated fields
|
// Deprecated fields
|
||||||
// Deprecated: OwnerID field is deprecated in DBVersion == 2
|
// Deprecated in DBVersion == 2
|
||||||
OwnerID UserID `json:"OwnerId"`
|
OwnerID UserID `json:"OwnerId,omitempty"`
|
||||||
// Deprecated: AccessLevel field is deprecated in DBVersion == 2
|
AccessLevel ResourceAccessLevel `json:"AccessLevel,omitempty"`
|
||||||
AccessLevel ResourceAccessLevel `json:"AccessLevel"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResourceControlType represents the type of resource associated to the resource control (volume, container, service).
|
// ResourceControlType represents the type of resource associated to the resource control (volume, container, service).
|
||||||
|
@ -325,6 +327,7 @@ type (
|
||||||
FileService interface {
|
FileService interface {
|
||||||
StoreTLSFile(folder string, fileType TLSFileType, r io.Reader) error
|
StoreTLSFile(folder string, fileType TLSFileType, r io.Reader) error
|
||||||
GetPathForTLSFile(folder string, fileType TLSFileType) (string, error)
|
GetPathForTLSFile(folder string, fileType TLSFileType) (string, error)
|
||||||
|
DeleteTLSFile(folder string, fileType TLSFileType) error
|
||||||
DeleteTLSFiles(folder string) error
|
DeleteTLSFiles(folder string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,7 +347,7 @@ const (
|
||||||
// APIVersion is the version number of the Portainer API.
|
// APIVersion is the version number of the Portainer API.
|
||||||
APIVersion = "1.14.0"
|
APIVersion = "1.14.0"
|
||||||
// DBVersion is the version number of the Portainer database.
|
// DBVersion is the version number of the Portainer database.
|
||||||
DBVersion = 3
|
DBVersion = 4
|
||||||
// DefaultTemplatesURL represents the default URL for the templates definitions.
|
// DefaultTemplatesURL represents the default URL for the templates definitions.
|
||||||
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json"
|
||||||
)
|
)
|
||||||
|
|
400
api/swagger.yaml
400
api/swagger.yaml
|
@ -1,38 +1,61 @@
|
||||||
---
|
---
|
||||||
swagger: "2.0"
|
swagger: "2.0"
|
||||||
info:
|
info:
|
||||||
description: "Portainer API is an HTTP API served by Portainer. It is used by the\
|
description: |
|
||||||
\ Portainer UI and everything you can do with the UI can be done using the HTTP\
|
Portainer API is an HTTP API served by Portainer. It is used by the Portainer UI and everything you can do with the UI can be done using the HTTP API.
|
||||||
\ API. \nExamples are available at https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8\
|
Examples are available at https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8
|
||||||
\ \nYou can find out more about Portainer at [http://portainer.io](http://portainer.io)\
|
You can find out more about Portainer at [http://portainer.io](http://portainer.io) and get some support on [Slack](http://portainer.io/slack/).
|
||||||
\ and get some support on [Slack](http://portainer.io/slack/).\n\n# Authentication\n\
|
|
||||||
\nMost of the API endpoints require to be authenticated as well as some level\
|
# Authentication
|
||||||
\ of authorization to be used.\nPortainer API uses JSON Web Token to manage authentication\
|
|
||||||
\ and thus requires you to provide a token in the **Authorization** header of\
|
Most of the API endpoints require to be authenticated as well as some level of authorization to be used.
|
||||||
\ each request\nwith the **Bearer** authentication mechanism.\n\nExample:\n```\n\
|
Portainer API uses JSON Web Token to manage authentication and thus requires you to provide a token in the **Authorization** header of each request
|
||||||
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE\n\
|
with the **Bearer** authentication mechanism.
|
||||||
```\n\n# Security\n\nEach API endpoint has an associated access policy, it is\
|
|
||||||
\ documented in the description of each endpoint.\n\nDifferent access policies\
|
Example:
|
||||||
\ are available:\n* Public access\n* Authenticated access\n* Restricted access\n\
|
```
|
||||||
* Administrator access\n\n### Public access\n\nNo authentication is required to\
|
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwidXNlcm5hbWUiOiJhZG1pbiIsInJvbGUiOjEsImV4cCI6MTQ5OTM3NjE1NH0.NJ6vE8FY1WG6jsRQzfMqeatJ4vh2TWAeeYfDhP71YEE
|
||||||
\ access the endpoints with this access policy.\n\n### Authenticated access\n\n\
|
```
|
||||||
Authentication is required to access the endpoints with this access policy.\n\n\
|
|
||||||
### Restricted access\n\nAuthentication is required to access the endpoints with\
|
# Security
|
||||||
\ this access policy.\nExtra-checks might be added to ensure access to the resource\
|
|
||||||
\ is granted. Returned data might also be filtered.\n\n### Administrator access\n\
|
Each API endpoint has an associated access policy, it is documented in the description of each endpoint.
|
||||||
\nAuthentication as well as an administrator role are required to access the endpoints\
|
|
||||||
\ with this access policy.\n\n# Execute Docker requests\n\nPortainer **DO NOT**\
|
Different access policies are available:
|
||||||
\ expose specific endpoints to manage your Docker resources (create a container,\
|
* Public access
|
||||||
\ remove a volume, etc...). \n\nInstead, it acts as a reverse-proxy to the Docker\
|
* Authenticated access
|
||||||
\ HTTP API. This means that you can execute Docker requests **via** the Portainer\
|
* Restricted access
|
||||||
\ HTTP API.\n\nTo do so, you can use the `/endpoints/{id}/docker` Portainer API\
|
* Administrator access
|
||||||
\ endpoint (which is not documented below due to Swagger limitations). This\n\
|
|
||||||
endpoint has a restricted access policy so you still need to be authenticated\
|
### Public access
|
||||||
\ to be able to query this endpoint. Any query on this endpoint will be proxied\
|
|
||||||
\ to the\nDocker API of the associated endpoint (requests and responses objects\
|
No authentication is required to access the endpoints with this access policy.
|
||||||
\ are the same as documented in the Docker API).\n\n**NOTE**: You can find more\
|
|
||||||
\ information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/)\
|
### Authenticated access
|
||||||
\ as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).\n"
|
|
||||||
|
Authentication is required to access the endpoints with this access policy.
|
||||||
|
|
||||||
|
### Restricted access
|
||||||
|
|
||||||
|
Authentication is required to access the endpoints with this access policy.
|
||||||
|
Extra-checks might be added to ensure access to the resource is granted. Returned data might also be filtered.
|
||||||
|
|
||||||
|
### Administrator access
|
||||||
|
|
||||||
|
Authentication as well as an administrator role are required to access the endpoints with this access policy.
|
||||||
|
|
||||||
|
# Execute Docker requests
|
||||||
|
|
||||||
|
Portainer **DO NOT** expose specific endpoints to manage your Docker resources (create a container, remove a volume, etc...).
|
||||||
|
|
||||||
|
Instead, it acts as a reverse-proxy to the Docker HTTP API. This means that you can execute Docker requests **via** the Portainer HTTP API.
|
||||||
|
|
||||||
|
To do so, you can use the `/endpoints/{id}/docker` Portainer API endpoint (which is not documented below due to Swagger limitations). This
|
||||||
|
endpoint has a restricted access policy so you still need to be authenticated to be able to query this endpoint. Any query on this endpoint will be proxied to the
|
||||||
|
Docker API of the associated endpoint (requests and responses objects are the same as documented in the Docker API).
|
||||||
|
|
||||||
|
**NOTE**: You can find more information on how to query the Docker API in the [Docker official documentation](https://docs.docker.com/engine/api/v1.30/) as well as in [this Portainer example](https://gist.github.com/deviantony/77026d402366b4b43fa5918d41bc42f8).
|
||||||
|
|
||||||
version: "1.14.0"
|
version: "1.14.0"
|
||||||
title: "Portainer API"
|
title: "Portainer API"
|
||||||
contact:
|
contact:
|
||||||
|
@ -75,8 +98,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "auth"
|
- "auth"
|
||||||
summary: "Authenticate a user"
|
summary: "Authenticate a user"
|
||||||
description: "Use this endpoint to authenticate against Portainer using a username\
|
description: |
|
||||||
\ and password. \n**Access policy**: public\n"
|
Use this endpoint to authenticate against Portainer using a username and password.
|
||||||
|
**Access policy**: public
|
||||||
operationId: "AuthenticateUser"
|
operationId: "AuthenticateUser"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -117,8 +141,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "dockerhub"
|
- "dockerhub"
|
||||||
summary: "Retrieve DockerHub information"
|
summary: "Retrieve DockerHub information"
|
||||||
description: "Use this endpoint to retrieve the information used to connect\
|
description: |
|
||||||
\ to the DockerHub \n**Access policy**: authenticated\n"
|
Use this endpoint to retrieve the information used to connect to the DockerHub
|
||||||
|
**Access policy**: authenticated
|
||||||
operationId: "DockerHubInspect"
|
operationId: "DockerHubInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -136,8 +161,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "dockerhub"
|
- "dockerhub"
|
||||||
summary: "Update DockerHub information"
|
summary: "Update DockerHub information"
|
||||||
description: "Use this endpoint to update the information used to connect to\
|
description: |
|
||||||
\ the DockerHub \n**Access policy**: administrator\n"
|
Use this endpoint to update the information used to connect to the DockerHub
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "DockerHubUpdate"
|
operationId: "DockerHubUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -169,9 +195,11 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "endpoints"
|
- "endpoints"
|
||||||
summary: "List endpoints"
|
summary: "List endpoints"
|
||||||
description: "List all endpoints based on the current user authorizations. Will\n\
|
description: |
|
||||||
return all endpoints if using an administrator account otherwise it will\n\
|
List all endpoints based on the current user authorizations. Will
|
||||||
only return authorized endpoints. \n**Access policy**: restricted \n"
|
return all endpoints if using an administrator account otherwise it will
|
||||||
|
only return authorized endpoints.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "EndpointList"
|
operationId: "EndpointList"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -189,8 +217,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "endpoints"
|
- "endpoints"
|
||||||
summary: "Create a new endpoint"
|
summary: "Create a new endpoint"
|
||||||
description: "Create a new endpoint that will be used to manage a Docker environment.\
|
description: |
|
||||||
\ \n**Access policy**: administrator\n"
|
Create a new endpoint that will be used to manage a Docker environment.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "EndpointCreate"
|
operationId: "EndpointCreate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -231,8 +260,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "endpoints"
|
- "endpoints"
|
||||||
summary: "Inspect an endpoint"
|
summary: "Inspect an endpoint"
|
||||||
description: "Retrieve details abount an endpoint. \n**Access policy**: administrator\
|
description: |
|
||||||
\ \n"
|
Retrieve details abount an endpoint.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "EndpointInspect"
|
operationId: "EndpointInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -269,7 +299,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "endpoints"
|
- "endpoints"
|
||||||
summary: "Update an endpoint"
|
summary: "Update an endpoint"
|
||||||
description: "Update an endpoint. \n**Access policy**: administrator\n"
|
description: |
|
||||||
|
Update an endpoint.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "EndpointUpdate"
|
operationId: "EndpointUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -319,7 +351,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "endpoints"
|
- "endpoints"
|
||||||
summary: "Remove an endpoint"
|
summary: "Remove an endpoint"
|
||||||
description: "Remove an endpoint. \n**Access policy**: administrator \n"
|
description: |
|
||||||
|
Remove an endpoint.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "EndpointDelete"
|
operationId: "EndpointDelete"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
|
@ -360,8 +394,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "endpoints"
|
- "endpoints"
|
||||||
summary: "Manage accesses to an endpoint"
|
summary: "Manage accesses to an endpoint"
|
||||||
description: "Manage user and team accesses to an endpoint. \n**Access policy**:\
|
description: |
|
||||||
\ administrator \n"
|
Manage user and team accesses to an endpoint.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "EndpointAccessUpdate"
|
operationId: "EndpointAccessUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -400,15 +435,17 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/registries:
|
/registries:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- "registries"
|
- "registries"
|
||||||
summary: "List registries"
|
summary: "List registries"
|
||||||
description: "List all registries based on the current user authorizations.\n\
|
description: |
|
||||||
Will return all registries if using an administrator account otherwise it\n\
|
List all registries based on the current user authorizations.
|
||||||
will only return authorized registries. \n**Access policy**: restricted \
|
Will return all registries if using an administrator account otherwise it
|
||||||
\ \n"
|
will only return authorized registries.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "RegistryList"
|
operationId: "RegistryList"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -426,8 +463,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "registries"
|
- "registries"
|
||||||
summary: "Create a new registry"
|
summary: "Create a new registry"
|
||||||
description: "Create a new registry. \n**Access policy**: administrator \
|
description: |
|
||||||
\ \n"
|
Create a new registry.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "RegistryCreate"
|
operationId: "RegistryCreate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -468,8 +506,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "registries"
|
- "registries"
|
||||||
summary: "Inspect a registry"
|
summary: "Inspect a registry"
|
||||||
description: "Retrieve details about a registry. \n**Access policy**: administrator\
|
description: |
|
||||||
\ \n"
|
Retrieve details about a registry.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "RegistryInspect"
|
operationId: "RegistryInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -506,7 +545,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "registries"
|
- "registries"
|
||||||
summary: "Update a registry"
|
summary: "Update a registry"
|
||||||
description: "Update a registry. \n**Access policy**: administrator \n"
|
description: |
|
||||||
|
Update a registry.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "RegistryUpdate"
|
operationId: "RegistryUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -563,8 +604,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "registries"
|
- "registries"
|
||||||
summary: "Remove a registry"
|
summary: "Remove a registry"
|
||||||
description: "Remove a registry. \n**Access policy**: administrator \
|
description: |
|
||||||
\ \n"
|
Remove a registry.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "RegistryDelete"
|
operationId: "RegistryDelete"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
|
@ -598,8 +640,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "registries"
|
- "registries"
|
||||||
summary: "Manage accesses to a registry"
|
summary: "Manage accesses to a registry"
|
||||||
description: "Manage user and team accesses to a registry. \n**Access policy**:\
|
description: |
|
||||||
\ administrator \n"
|
Manage user and team accesses to a registry.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "RegistryAccessUpdate"
|
operationId: "RegistryAccessUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -643,8 +686,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "resource_controls"
|
- "resource_controls"
|
||||||
summary: "Create a new resource control"
|
summary: "Create a new resource control"
|
||||||
description: "Create a new resource control to restrict access to a Docker resource.\
|
description: |
|
||||||
\ \n**Access policy**: restricted \n"
|
Create a new resource control to restrict access to a Docker resource.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "ResourceControlCreate"
|
operationId: "ResourceControlCreate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -690,8 +734,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "resource_controls"
|
- "resource_controls"
|
||||||
summary: "Update a resource control"
|
summary: "Update a resource control"
|
||||||
description: "Update a resource control. \n**Access policy**: restricted \
|
description: |
|
||||||
\ \n"
|
Update a resource control.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "ResourceControlUpdate"
|
operationId: "ResourceControlUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -741,8 +786,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "resource_controls"
|
- "resource_controls"
|
||||||
summary: "Remove a resource control"
|
summary: "Remove a resource control"
|
||||||
description: "Remove a resource control. \n**Access policy**: restricted \
|
description: |
|
||||||
\ \n"
|
Remove a resource control.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "ResourceControlDelete"
|
operationId: "ResourceControlDelete"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
|
@ -783,8 +829,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "settings"
|
- "settings"
|
||||||
summary: "Retrieve Portainer settings"
|
summary: "Retrieve Portainer settings"
|
||||||
description: "Retrieve Portainer settings. \n**Access policy**: administrator\
|
description: |
|
||||||
\ \n"
|
Retrieve Portainer settings.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "SettingsInspect"
|
operationId: "SettingsInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -802,8 +849,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "settings"
|
- "settings"
|
||||||
summary: "Update Portainer settings"
|
summary: "Update Portainer settings"
|
||||||
description: "Update Portainer settings. \n**Access policy**: administrator\
|
description: |
|
||||||
\ \n"
|
Update Portainer settings.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "SettingsUpdate"
|
operationId: "SettingsUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -835,9 +883,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "settings"
|
- "settings"
|
||||||
summary: "Retrieve Portainer public settings"
|
summary: "Retrieve Portainer public settings"
|
||||||
description: "Retrieve public settings. Returns a small set of settings that\
|
description: |
|
||||||
\ are not reserved to administrators only. \n**Access policy**: public \
|
Retrieve public settings. Returns a small set of settings that are not reserved to administrators only.
|
||||||
\ \n"
|
**Access policy**: public
|
||||||
operationId: "PublicSettingsInspect"
|
operationId: "PublicSettingsInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -856,8 +904,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "settings"
|
- "settings"
|
||||||
summary: "Test LDAP connectivity"
|
summary: "Test LDAP connectivity"
|
||||||
description: "Test LDAP connectivity using LDAP details. \n**Access policy**:\
|
description: |
|
||||||
\ administrator \n"
|
Test LDAP connectivity using LDAP details.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "SettingsLDAPCheck"
|
operationId: "SettingsLDAPCheck"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -889,8 +938,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "status"
|
- "status"
|
||||||
summary: "Check Portainer status"
|
summary: "Check Portainer status"
|
||||||
description: "Retrieve Portainer status. \n**Access policy**: public \
|
description: |
|
||||||
\ \n"
|
Retrieve Portainer status.
|
||||||
|
**Access policy**: public
|
||||||
operationId: "StatusInspect"
|
operationId: "StatusInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -909,9 +959,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "List users"
|
summary: "List users"
|
||||||
description: "List Portainer users. Non-administrator users will only be able\
|
description: |
|
||||||
\ to list other non-administrator user accounts. \n**Access policy**: restricted\
|
List Portainer users. Non-administrator users will only be able to list other non-administrator user accounts.
|
||||||
\ \n"
|
**Access policy**: restricted
|
||||||
operationId: "UserList"
|
operationId: "UserList"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -929,9 +979,10 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Create a new user"
|
summary: "Create a new user"
|
||||||
description: "Create a new Portainer user. Only team leaders and administrators\
|
description: |
|
||||||
\ can create users. Only administrators can\ncreate an administrator user\
|
Create a new Portainer user. Only team leaders and administrators can create users. Only administrators can
|
||||||
\ account. \n**Access policy**: restricted \n"
|
create an administrator user account.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "UserCreate"
|
operationId: "UserCreate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -979,8 +1030,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Inspect a user"
|
summary: "Inspect a user"
|
||||||
description: "Retrieve details about a user. \n**Access policy**: administrator\
|
description: |
|
||||||
\ \n"
|
Retrieve details about a user.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "UserInspect"
|
operationId: "UserInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1017,8 +1069,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Update a user"
|
summary: "Update a user"
|
||||||
description: "Update user details. A regular user account can only update his\
|
description: |
|
||||||
\ details. \n**Access policy**: authenticated \n"
|
Update user details. A regular user account can only update his details.
|
||||||
|
**Access policy**: authenticated
|
||||||
operationId: "UserUpdate"
|
operationId: "UserUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1068,7 +1121,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Remove a user"
|
summary: "Remove a user"
|
||||||
description: "Remove a user. \n**Access policy**: administrator \n"
|
description: |
|
||||||
|
Remove a user.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "UserDelete"
|
operationId: "UserDelete"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
|
@ -1102,8 +1157,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Inspect a user memberships"
|
summary: "Inspect a user memberships"
|
||||||
description: "Inspect a user memberships. \n**Access policy**: authenticated\
|
description: |
|
||||||
\ \n"
|
Inspect a user memberships.
|
||||||
|
**Access policy**: authenticated
|
||||||
operationId: "UserMembershipsInspect"
|
operationId: "UserMembershipsInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1136,13 +1192,15 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/users/{id}/passwd:
|
/users/{id}/passwd:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Check password validity for a user"
|
summary: "Check password validity for a user"
|
||||||
description: "Check if the submitted password is valid for the specified user.\
|
description: |
|
||||||
\ \n**Access policy**: authenticated \n"
|
Check if the submitted password is valid for the specified user.
|
||||||
|
**Access policy**: authenticated
|
||||||
operationId: "UserPasswordCheck"
|
operationId: "UserPasswordCheck"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1183,13 +1241,15 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/users/admin/check:
|
/users/admin/check:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Check administrator account existence"
|
summary: "Check administrator account existence"
|
||||||
description: "Check if an administrator account exists in the database.\n**Access\
|
description: |
|
||||||
\ policy**: public \n"
|
Check if an administrator account exists in the database.
|
||||||
|
**Access policy**: public
|
||||||
operationId: "UserAdminCheck"
|
operationId: "UserAdminCheck"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1210,13 +1270,15 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/users/admin/init:
|
/users/admin/init:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- "users"
|
- "users"
|
||||||
summary: "Initialize administrator account"
|
summary: "Initialize administrator account"
|
||||||
description: "Initialize the 'admin' user account.\n**Access policy**: public\
|
description: |
|
||||||
\ \n"
|
Initialize the 'admin' user account.
|
||||||
|
**Access policy**: public
|
||||||
operationId: "UserAdminInit"
|
operationId: "UserAdminInit"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1250,34 +1312,35 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/upload/tls/{certificate}:
|
/upload/tls/{certificate}:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- "upload"
|
- "upload"
|
||||||
summary: "Upload TLS files"
|
summary: "Upload TLS files"
|
||||||
description: "Use this endpoint to upload TLS files. \n**Access policy**: administrator\n"
|
description: |
|
||||||
|
Use this endpoint to upload TLS files.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "UploadTLS"
|
operationId: "UploadTLS"
|
||||||
consumes:
|
consumes:
|
||||||
- "multipart/form-data"
|
- multipart/form-data
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "certificate"
|
- in: "path"
|
||||||
in: "path"
|
name: "certificate"
|
||||||
description: "TLS file type. Valid values are 'ca', 'cert' or 'key'."
|
description: "TLS file type. Valid values are 'ca', 'cert' or 'key'."
|
||||||
required: true
|
required: true
|
||||||
type: "string"
|
type: "string"
|
||||||
- name: "folder"
|
- in: "query"
|
||||||
in: "query"
|
name: "folder"
|
||||||
description: "Folder where the TLS file will be stored. Will be created if\
|
description: "Folder where the TLS file will be stored. Will be created if not existing."
|
||||||
\ not existing."
|
|
||||||
required: true
|
required: true
|
||||||
type: "string"
|
type: "string"
|
||||||
- name: "file"
|
- in: "formData"
|
||||||
in: "formData"
|
name: "file"
|
||||||
description: "The file to upload."
|
|
||||||
required: false
|
|
||||||
type: "file"
|
type: "file"
|
||||||
|
description: "The file to upload."
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
description: "Success"
|
description: "Success"
|
||||||
|
@ -1292,13 +1355,15 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/teams:
|
/teams:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- "teams"
|
- "teams"
|
||||||
summary: "List teams"
|
summary: "List teams"
|
||||||
description: "List teams. For non-administrator users, will only list the teams\
|
description: |
|
||||||
\ they are member of. \n**Access policy**: restricted \n"
|
List teams. For non-administrator users, will only list the teams they are member of.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "TeamList"
|
operationId: "TeamList"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1316,8 +1381,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "teams"
|
- "teams"
|
||||||
summary: "Create a new team"
|
summary: "Create a new team"
|
||||||
description: "Create a new team. \n**Access policy**: administrator \
|
description: |
|
||||||
\ \n"
|
Create a new team.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "TeamCreate"
|
operationId: "TeamCreate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1365,8 +1431,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "teams"
|
- "teams"
|
||||||
summary: "Inspect a team"
|
summary: "Inspect a team"
|
||||||
description: "Retrieve details about a team. Access is only available for administrator\
|
description: |
|
||||||
\ and leaders of that team. \n**Access policy**: restricted \n"
|
Retrieve details about a team. Access is only available for administrator and leaders of that team.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "TeamInspect"
|
operationId: "TeamInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1410,8 +1477,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "teams"
|
- "teams"
|
||||||
summary: "Update a team"
|
summary: "Update a team"
|
||||||
description: "Update a team. \n**Access policy**: administrator \
|
description: |
|
||||||
\ \n"
|
Update a team.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "TeamUpdate"
|
operationId: "TeamUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1454,7 +1522,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "teams"
|
- "teams"
|
||||||
summary: "Remove a team"
|
summary: "Remove a team"
|
||||||
description: "Remove a team. \n**Access policy**: administrator \n"
|
description: |
|
||||||
|
Remove a team.
|
||||||
|
**Access policy**: administrator
|
||||||
operationId: "TeamDelete"
|
operationId: "TeamDelete"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
|
@ -1483,13 +1553,15 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/teams/{id}/memberships:
|
/teams/{id}/memberships:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- "teams"
|
- "teams"
|
||||||
summary: "Inspect a team memberships"
|
summary: "Inspect a team memberships"
|
||||||
description: "Inspect a team memberships. Access is only available for administrator\
|
description: |
|
||||||
\ and leaders of that team. \n**Access policy**: restricted \n"
|
Inspect a team memberships. Access is only available for administrator and leaders of that team.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "TeamMembershipsInspect"
|
operationId: "TeamMembershipsInspect"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1522,13 +1594,15 @@ paths:
|
||||||
description: "Server error"
|
description: "Server error"
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/definitions/GenericError"
|
$ref: "#/definitions/GenericError"
|
||||||
|
|
||||||
/team_memberships:
|
/team_memberships:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
- "team_memberships"
|
- "team_memberships"
|
||||||
summary: "List team memberships"
|
summary: "List team memberships"
|
||||||
description: "List team memberships. Access is only available to administrators\
|
description: |
|
||||||
\ and team leaders. \n**Access policy**: restricted \n"
|
List team memberships. Access is only available to administrators and team leaders.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "TeamMembershipList"
|
operationId: "TeamMembershipList"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1553,8 +1627,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "team_memberships"
|
- "team_memberships"
|
||||||
summary: "Create a new team membership"
|
summary: "Create a new team membership"
|
||||||
description: "Create a new team memberships. Access is only available to administrators\
|
description: |
|
||||||
\ leaders of the associated team. \n**Access policy**: restricted \n"
|
Create a new team memberships. Access is only available to administrators leaders of the associated team.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "TeamMembershipCreate"
|
operationId: "TeamMembershipCreate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1602,9 +1677,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "team_memberships"
|
- "team_memberships"
|
||||||
summary: "Update a team membership"
|
summary: "Update a team membership"
|
||||||
description: "Update a team membership. Access is only available to administrators\
|
description: |
|
||||||
\ leaders of the associated team. \n**Access policy**: restricted \
|
Update a team membership. Access is only available to administrators leaders of the associated team.
|
||||||
\ \n"
|
**Access policy**: restricted
|
||||||
operationId: "TeamMembershipUpdate"
|
operationId: "TeamMembershipUpdate"
|
||||||
consumes:
|
consumes:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
|
@ -1654,8 +1729,9 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "team_memberships"
|
- "team_memberships"
|
||||||
summary: "Remove a team membership"
|
summary: "Remove a team membership"
|
||||||
description: "Remove a team membership. Access is only available to administrators\
|
description: |
|
||||||
\ leaders of the associated team. \n**Access policy**: restricted \n"
|
Remove a team membership. Access is only available to administrators leaders of the associated team.
|
||||||
|
**Access policy**: restricted
|
||||||
operationId: "TeamMembershipDelete"
|
operationId: "TeamMembershipDelete"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "id"
|
- name: "id"
|
||||||
|
@ -1696,17 +1772,18 @@ paths:
|
||||||
tags:
|
tags:
|
||||||
- "templates"
|
- "templates"
|
||||||
summary: "Retrieve App templates"
|
summary: "Retrieve App templates"
|
||||||
description: "Retrieve App templates. \nYou can find more information about\
|
description: |
|
||||||
\ the format at http://portainer.readthedocs.io/en/stable/templates.html \
|
Retrieve App templates.
|
||||||
\ \n**Access policy**: authenticated \n"
|
You can find more information about the format at http://portainer.readthedocs.io/en/stable/templates.html
|
||||||
|
**Access policy**: authenticated
|
||||||
operationId: "TemplateList"
|
operationId: "TemplateList"
|
||||||
produces:
|
produces:
|
||||||
- "application/json"
|
- "application/json"
|
||||||
parameters:
|
parameters:
|
||||||
- name: "key"
|
- name: "key"
|
||||||
in: "query"
|
in: "query"
|
||||||
description: "Templates key. Valid values are 'container' or 'linuxserver.io'."
|
|
||||||
required: true
|
required: true
|
||||||
|
description: "Templates key. Valid values are 'container' or 'linuxserver.io'."
|
||||||
type: "string"
|
type: "string"
|
||||||
responses:
|
responses:
|
||||||
200:
|
200:
|
||||||
|
@ -1811,8 +1888,8 @@ definitions:
|
||||||
AuthenticationMethod:
|
AuthenticationMethod:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Active authentication method for the Portainer instance. Valid\
|
description: "Active authentication method for the Portainer instance. Valid values are: 1 for managed or 2 for LDAP."
|
||||||
\ values are: 1 for managed or 2 for LDAP."
|
|
||||||
TLSConfiguration:
|
TLSConfiguration:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -1836,14 +1913,14 @@ definitions:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "/data/tls/key.pem"
|
example: "/data/tls/key.pem"
|
||||||
description: "Path to the TLS client key file"
|
description: "Path to the TLS client key file"
|
||||||
|
|
||||||
LDAPSearchSettings:
|
LDAPSearchSettings:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
BaseDN:
|
BaseDN:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "dc=ldap,dc=domain,dc=tld"
|
example: "dc=ldap,dc=domain,dc=tld"
|
||||||
description: "The distinguished name of the element from which the LDAP server\
|
description: "The distinguished name of the element from which the LDAP server will search for users"
|
||||||
\ will search for users"
|
|
||||||
Filter:
|
Filter:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "(objectClass=account)"
|
example: "(objectClass=account)"
|
||||||
|
@ -1852,6 +1929,7 @@ definitions:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "uid"
|
example: "uid"
|
||||||
description: "LDAP attribute which denotes the username"
|
description: "LDAP attribute which denotes the username"
|
||||||
|
|
||||||
LDAPSettings:
|
LDAPSettings:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -1877,6 +1955,7 @@ definitions:
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
$ref: "#/definitions/LDAPSearchSettings"
|
$ref: "#/definitions/LDAPSearchSettings"
|
||||||
|
|
||||||
Settings:
|
Settings:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -1905,8 +1984,7 @@ definitions:
|
||||||
AuthenticationMethod:
|
AuthenticationMethod:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Active authentication method for the Portainer instance. Valid\
|
description: "Active authentication method for the Portainer instance. Valid values are: 1 for managed or 2 for LDAP."
|
||||||
\ values are: 1 for managed or 2 for LDAP."
|
|
||||||
LDAPSettings:
|
LDAPSettings:
|
||||||
$ref: "#/definitions/LDAPSettings"
|
$ref: "#/definitions/LDAPSettings"
|
||||||
Settings_BlackListedLabels:
|
Settings_BlackListedLabels:
|
||||||
|
@ -2072,6 +2150,14 @@ definitions:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
description: "Require TLS to connect against this endpoint"
|
description: "Require TLS to connect against this endpoint"
|
||||||
|
TLSSkipVerify:
|
||||||
|
type: "boolean"
|
||||||
|
example: false
|
||||||
|
description: "Skip server verification when using TLS"
|
||||||
|
TLSSkipClientVerify:
|
||||||
|
type: "boolean"
|
||||||
|
example: false
|
||||||
|
description: "Skip client verification when using TLS"
|
||||||
EndpointCreateResponse:
|
EndpointCreateResponse:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -2103,6 +2189,14 @@ definitions:
|
||||||
type: "boolean"
|
type: "boolean"
|
||||||
example: true
|
example: true
|
||||||
description: "Require TLS to connect against this endpoint"
|
description: "Require TLS to connect against this endpoint"
|
||||||
|
TLSSkipVerify:
|
||||||
|
type: "boolean"
|
||||||
|
example: false
|
||||||
|
description: "Skip server verification when using TLS"
|
||||||
|
TLSSkipClientVerify:
|
||||||
|
type: "boolean"
|
||||||
|
example: false
|
||||||
|
description: "Skip client verification when using TLS"
|
||||||
EndpointAccessUpdateRequest:
|
EndpointAccessUpdateRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -2269,8 +2363,8 @@ definitions:
|
||||||
SettingsUpdateRequest:
|
SettingsUpdateRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
required:
|
required:
|
||||||
- "AuthenticationMethod"
|
|
||||||
- "TemplatesURL"
|
- "TemplatesURL"
|
||||||
|
- "AuthenticationMethod"
|
||||||
properties:
|
properties:
|
||||||
TemplatesURL:
|
TemplatesURL:
|
||||||
type: "string"
|
type: "string"
|
||||||
|
@ -2297,8 +2391,7 @@ definitions:
|
||||||
AuthenticationMethod:
|
AuthenticationMethod:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Active authentication method for the Portainer instance. Valid\
|
description: "Active authentication method for the Portainer instance. Valid values are: 1 for managed or 2 for LDAP."
|
||||||
\ values are: 1 for managed or 2 for LDAP."
|
|
||||||
LDAPSettings:
|
LDAPSettings:
|
||||||
$ref: "#/definitions/LDAPSettings"
|
$ref: "#/definitions/LDAPSettings"
|
||||||
UserCreateRequest:
|
UserCreateRequest:
|
||||||
|
@ -2395,12 +2488,13 @@ definitions:
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
$ref: "#/definitions/TeamMembership"
|
$ref: "#/definitions/TeamMembership"
|
||||||
|
|
||||||
TeamMembershipCreateRequest:
|
TeamMembershipCreateRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
required:
|
required:
|
||||||
- "Role"
|
|
||||||
- "TeamID"
|
|
||||||
- "UserID"
|
- "UserID"
|
||||||
|
- "TeamID"
|
||||||
|
- "Role"
|
||||||
properties:
|
properties:
|
||||||
UserID:
|
UserID:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
|
@ -2413,8 +2507,7 @@ definitions:
|
||||||
Role:
|
Role:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Role for the user inside the team (1 for leader and 2 for regular\
|
description: "Role for the user inside the team (1 for leader and 2 for regular member)"
|
||||||
\ member)"
|
|
||||||
TeamMembershipCreateResponse:
|
TeamMembershipCreateResponse:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -2429,9 +2522,9 @@ definitions:
|
||||||
TeamMembershipUpdateRequest:
|
TeamMembershipUpdateRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
required:
|
required:
|
||||||
- "Role"
|
|
||||||
- "TeamID"
|
|
||||||
- "UserID"
|
- "UserID"
|
||||||
|
- "TeamID"
|
||||||
|
- "Role"
|
||||||
properties:
|
properties:
|
||||||
UserID:
|
UserID:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
|
@ -2444,8 +2537,7 @@ definitions:
|
||||||
Role:
|
Role:
|
||||||
type: "integer"
|
type: "integer"
|
||||||
example: 1
|
example: 1
|
||||||
description: "Role for the user inside the team (1 for leader and 2 for regular\
|
description: "Role for the user inside the team (1 for leader and 2 for regular member)"
|
||||||
\ member)"
|
|
||||||
SettingsLDAPCheckRequest:
|
SettingsLDAPCheckRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
|
@ -2454,14 +2546,10 @@ definitions:
|
||||||
UserAdminInitRequest:
|
UserAdminInitRequest:
|
||||||
type: "object"
|
type: "object"
|
||||||
properties:
|
properties:
|
||||||
Username:
|
|
||||||
type: "string"
|
|
||||||
example: "admin"
|
|
||||||
description: "Username of the initial administrator account"
|
|
||||||
Password:
|
Password:
|
||||||
type: "string"
|
type: "string"
|
||||||
example: "admin-password"
|
example: "admin-password"
|
||||||
description: "Password of the initial administrator account"
|
description: "Password for the admin user"
|
||||||
TemplateListResponse:
|
TemplateListResponse:
|
||||||
type: "array"
|
type: "array"
|
||||||
items:
|
items:
|
||||||
|
|
|
@ -12,6 +12,9 @@
|
||||||
<rd-widget>
|
<rd-widget>
|
||||||
<rd-widget-body>
|
<rd-widget-body>
|
||||||
<form class="form-horizontal">
|
<form class="form-horizontal">
|
||||||
|
<div class="col-sm-12 form-section-title">
|
||||||
|
Configuration
|
||||||
|
</div>
|
||||||
<!-- name-input -->
|
<!-- name-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="container_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
|
<label for="container_name" class="col-sm-3 col-lg-2 control-label text-left">Name</label>
|
||||||
|
@ -42,73 +45,19 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !endpoint-public-url-input -->
|
<!-- !endpoint-public-url-input -->
|
||||||
<!-- tls-checkbox -->
|
<!-- endpoint-security -->
|
||||||
<div class="form-group" ng-if="endpointType === 'remote'">
|
<div ng-if="endpointType === 'remote'">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12 form-section-title">
|
||||||
<label for="tls" class="control-label text-left">
|
Security
|
||||||
TLS
|
|
||||||
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the Docker endpoint."></portainer-tooltip>
|
|
||||||
</label>
|
|
||||||
<label class="switch" style="margin-left: 20px;">
|
|
||||||
<input type="checkbox" ng-model="endpoint.TLS"><i></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
<por-endpoint-security form-data="formValues.SecurityFormData" endpoint="endpoint"></por-endpoint-security>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tls-checkbox -->
|
<!-- !endpoint-security -->
|
||||||
<!-- tls-certs -->
|
|
||||||
<div ng-if="endpoint.TLS">
|
|
||||||
<!-- ca-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-2 control-label text-left">TLS CA certificate</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
|
|
||||||
<span style="margin-left: 5px;">
|
|
||||||
<span ng-if="formValues.TLSCACert !== endpoint.TLSCACert">{{ formValues.TLSCACert.name }}</span>
|
|
||||||
<i class="fa fa-check green-icon" ng-if="formValues.TLSCACert && formValues.TLSCACert === endpoint.TLSCACert" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !ca-input -->
|
|
||||||
<!-- cert-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tls_cert" class="col-sm-2 control-label text-left">TLS certificate</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCert">Select file</button>
|
|
||||||
<span style="margin-left: 5px;">
|
|
||||||
<span ng-if="formValues.TLSCert !== endpoint.TLSCert">{{ formValues.TLSCert.name }}</span>
|
|
||||||
<i class="fa fa-check green-icon" ng-if="formValues.TLSCert && formValues.TLSCert === endpoint.TLSCert" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCert" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !cert-input -->
|
|
||||||
<!-- key-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-2 control-label text-left">TLS key</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSKey">Select file</button>
|
|
||||||
<span style="margin-left: 5px;">
|
|
||||||
<span ng-if="formValues.TLSKey !== endpoint.TLSKey">{{ formValues.TLSKey.name }}</span>
|
|
||||||
<i class="fa fa-check green-icon" ng-if="formValues.TLSKey && formValues.TLSKey === endpoint.TLSKey" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSKey" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !key-input -->
|
|
||||||
</div>
|
|
||||||
<!-- !tls-certs -->
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!endpoint.Name || !endpoint.URL || (endpoint.TLS && (!formValues.TLSCACert || !formValues.TLSCert || !formValues.TLSKey))" ng-click="updateEndpoint()">Update endpoint</button>
|
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!endpoint.Name || !endpoint.URL || (endpoint.TLS && ((endpoint.TLSVerify && !formValues.TLSCACert) || (endpoint.TLSClientCert && (!formValues.TLSCert || !formValues.TLSKey))))" ng-click="updateEndpoint()">Update endpoint</button>
|
||||||
<a type="button" class="btn btn-default btn-sm" ui-sref="endpoints">Cancel</a>
|
<a type="button" class="btn btn-default btn-sm" ui-sref="endpoints">Cancel</a>
|
||||||
<i id="updateEndpointSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
<i id="updateResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||||
<span class="text-danger" ng-if="state.error" style="margin: 5px;">
|
|
||||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.error }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
@ -7,35 +7,41 @@ function ($scope, $state, $stateParams, $filter, EndpointService, Notifications)
|
||||||
}
|
}
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
error: '',
|
|
||||||
uploadInProgress: false
|
uploadInProgress: false
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.formValues = {
|
$scope.formValues = {
|
||||||
TLSCACert: null,
|
SecurityFormData: new EndpointSecurityFormData()
|
||||||
TLSCert: null,
|
|
||||||
TLSKey: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.updateEndpoint = function() {
|
$scope.updateEndpoint = function() {
|
||||||
var ID = $scope.endpoint.Id;
|
var endpoint = $scope.endpoint;
|
||||||
|
var securityData = $scope.formValues.SecurityFormData;
|
||||||
|
var TLS = securityData.TLS;
|
||||||
|
var TLSMode = securityData.TLSMode;
|
||||||
|
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
||||||
|
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
||||||
|
|
||||||
var endpointParams = {
|
var endpointParams = {
|
||||||
name: $scope.endpoint.Name,
|
name: endpoint.Name,
|
||||||
URL: $scope.endpoint.URL,
|
URL: endpoint.URL,
|
||||||
PublicURL: $scope.endpoint.PublicURL,
|
PublicURL: endpoint.PublicURL,
|
||||||
TLS: $scope.endpoint.TLS,
|
TLS: TLS,
|
||||||
TLSCACert: $scope.formValues.TLSCACert !== $scope.endpoint.TLSCACert ? $scope.formValues.TLSCACert : null,
|
TLSSkipVerify: TLSSkipVerify,
|
||||||
TLSCert: $scope.formValues.TLSCert !== $scope.endpoint.TLSCert ? $scope.formValues.TLSCert : null,
|
TLSSkipClientVerify: TLSSkipClientVerify,
|
||||||
TLSKey: $scope.formValues.TLSKey !== $scope.endpoint.TLSKey ? $scope.formValues.TLSKey : null,
|
TLSCACert: TLSSkipVerify || securityData.TLSCACert === endpoint.TLSConfig.TLSCACert ? null : securityData.TLSCACert,
|
||||||
|
TLSCert: TLSSkipClientVerify || securityData.TLSCert === endpoint.TLSConfig.TLSCert ? null : securityData.TLSCert,
|
||||||
|
TLSKey: TLSSkipClientVerify || securityData.TLSKey === endpoint.TLSConfig.TLSKey ? null : securityData.TLSKey,
|
||||||
type: $scope.endpointType
|
type: $scope.endpointType
|
||||||
};
|
};
|
||||||
|
|
||||||
EndpointService.updateEndpoint(ID, endpointParams)
|
$('updateResourceSpinner').show();
|
||||||
|
EndpointService.updateEndpoint(endpoint.Id, endpointParams)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
Notifications.success('Endpoint updated', $scope.endpoint.Name);
|
Notifications.success('Endpoint updated', $scope.endpoint.Name);
|
||||||
$state.go('endpoints');
|
$state.go('endpoints');
|
||||||
}, function error(err) {
|
}, function error(err) {
|
||||||
$scope.state.error = err.msg;
|
Notifications.error('Failure', err, 'Unable to update endpoint');
|
||||||
}, function update(evt) {
|
}, function update(evt) {
|
||||||
if (evt.upload) {
|
if (evt.upload) {
|
||||||
$scope.state.uploadInProgress = evt.upload;
|
$scope.state.uploadInProgress = evt.upload;
|
||||||
|
@ -43,25 +49,27 @@ function ($scope, $state, $stateParams, $filter, EndpointService, Notifications)
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
function getEndpoint(endpointID) {
|
function initView() {
|
||||||
$('#loadingViewSpinner').show();
|
$('#loadingViewSpinner').show();
|
||||||
EndpointService.endpoint($stateParams.id).then(function success(data) {
|
EndpointService.endpoint($stateParams.id)
|
||||||
$('#loadingViewSpinner').hide();
|
.then(function success(data) {
|
||||||
$scope.endpoint = data;
|
var endpoint = data;
|
||||||
if (data.URL.indexOf('unix://') === 0) {
|
endpoint.URL = $filter('stripprotocol')(endpoint.URL);
|
||||||
|
$scope.endpoint = endpoint;
|
||||||
|
|
||||||
|
if (endpoint.URL.indexOf('unix://') === 0) {
|
||||||
$scope.endpointType = 'local';
|
$scope.endpointType = 'local';
|
||||||
} else {
|
} else {
|
||||||
$scope.endpointType = 'remote';
|
$scope.endpointType = 'remote';
|
||||||
}
|
}
|
||||||
$scope.endpoint.URL = $filter('stripprotocol')(data.URL);
|
})
|
||||||
$scope.formValues.TLSCACert = data.TLSCACert;
|
.catch(function error(err) {
|
||||||
$scope.formValues.TLSCert = data.TLSCert;
|
|
||||||
$scope.formValues.TLSKey = data.TLSKey;
|
|
||||||
}, function error(err) {
|
|
||||||
$('#loadingViewSpinner').hide();
|
|
||||||
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');
|
Notifications.error('Failure', err, 'Unable to retrieve endpoint details');
|
||||||
|
})
|
||||||
|
.finally(function final() {
|
||||||
|
$('#loadingViewSpinner').hide();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getEndpoint($stateParams.id);
|
initView();
|
||||||
}]);
|
}]);
|
||||||
|
|
|
@ -60,71 +60,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !endpoint-public-url-input -->
|
<!-- !endpoint-public-url-input -->
|
||||||
<!-- tls-checkbox -->
|
<!-- endpoint-security -->
|
||||||
|
<por-endpoint-security form-data="formValues.SecurityFormData"></por-endpoint-security>
|
||||||
|
<!-- !endpoint-security -->
|
||||||
|
<!-- actions -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<label for="tls" class="control-label text-left">
|
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!formValues.Name || !formValues.URL || (formValues.TLS && ((formValues.TLSVerify && !formValues.TLSCACert) || (formValues.TLSClientCert && (!formValues.TLSCert || !formValues.TLSKey))))" ng-click="addEndpoint()"><i class="fa fa-plus" aria-hidden="true"></i> Add endpoint</button>
|
||||||
TLS
|
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||||
<portainer-tooltip position="bottom" message="Enable this option if you need to specify TLS certificates to connect to the Docker endpoint."></portainer-tooltip>
|
|
||||||
</label>
|
|
||||||
<label class="switch" style="margin-left: 20px;">
|
|
||||||
<input type="checkbox" ng-model="formValues.TLS"><i></i>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !tls-checkbox -->
|
|
||||||
<!-- tls-certs -->
|
|
||||||
<div ng-if="formValues.TLS">
|
|
||||||
<!-- ca-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-2 control-label text-left">TLS CA certificate</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
|
|
||||||
<span style="margin-left: 5px;">
|
|
||||||
{{ formValues.TLSCACert.name }}
|
|
||||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCACert" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !ca-input -->
|
|
||||||
<!-- cert-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="tls_cert" class="col-sm-2 control-label text-left">TLS certificate</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCert">Select file</button>
|
|
||||||
<span style="margin-left: 5px;">
|
|
||||||
{{ formValues.TLSCert.name }}
|
|
||||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSCert" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !cert-input -->
|
|
||||||
<!-- key-input -->
|
|
||||||
<div class="form-group">
|
|
||||||
<label class="col-sm-2 control-label text-left">TLS key</label>
|
|
||||||
<div class="col-sm-10">
|
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSKey">Select file</button>
|
|
||||||
<span style="margin-left: 5px;">
|
|
||||||
{{ formValues.TLSKey.name }}
|
|
||||||
<i class="fa fa-times red-icon" ng-if="!formValues.TLSKey" aria-hidden="true"></i>
|
|
||||||
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<!-- !key-input -->
|
|
||||||
</div>
|
|
||||||
<!-- !tls-certs -->
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-sm-12">
|
|
||||||
<button type="button" class="btn btn-primary btn-sm" ng-disabled="!formValues.Name || !formValues.URL || (formValues.TLS && (!formValues.TLSCACert || !formValues.TLSCert || !formValues.TLSKey))" ng-click="addEndpoint()">Add endpoint</button>
|
|
||||||
<i id="createEndpointSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
|
||||||
<span class="text-danger" ng-if="state.error" style="margin: 5px;">
|
|
||||||
<i class="fa fa-exclamation-circle" aria-hidden="true"></i> {{ state.error }}
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- !actions -->
|
||||||
</form>
|
</form>
|
||||||
</rd-widget-body>
|
</rd-widget-body>
|
||||||
</rd-widget>
|
</rd-widget>
|
||||||
|
|
|
@ -2,7 +2,6 @@ angular.module('endpoints', [])
|
||||||
.controller('EndpointsController', ['$scope', '$state', 'EndpointService', 'EndpointProvider', 'Notifications', 'Pagination',
|
.controller('EndpointsController', ['$scope', '$state', 'EndpointService', 'EndpointProvider', 'Notifications', 'Pagination',
|
||||||
function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagination) {
|
function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagination) {
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
error: '',
|
|
||||||
uploadInProgress: false,
|
uploadInProgress: false,
|
||||||
selectedItemCount: 0,
|
selectedItemCount: 0,
|
||||||
pagination_count: Pagination.getPaginationCount('endpoints')
|
pagination_count: Pagination.getPaginationCount('endpoints')
|
||||||
|
@ -14,10 +13,7 @@ function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagi
|
||||||
Name: '',
|
Name: '',
|
||||||
URL: '',
|
URL: '',
|
||||||
PublicURL: '',
|
PublicURL: '',
|
||||||
TLS: false,
|
SecurityFormData: new EndpointSecurityFormData()
|
||||||
TLSCACert: null,
|
|
||||||
TLSCert: null,
|
|
||||||
TLSKey: null
|
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.order = function(sortType) {
|
$scope.order = function(sortType) {
|
||||||
|
@ -47,23 +43,28 @@ function ($scope, $state, EndpointService, EndpointProvider, Notifications, Pagi
|
||||||
};
|
};
|
||||||
|
|
||||||
$scope.addEndpoint = function() {
|
$scope.addEndpoint = function() {
|
||||||
$scope.state.error = '';
|
|
||||||
var name = $scope.formValues.Name;
|
var name = $scope.formValues.Name;
|
||||||
var URL = $scope.formValues.URL;
|
var URL = $scope.formValues.URL;
|
||||||
var PublicURL = $scope.formValues.PublicURL;
|
var PublicURL = $scope.formValues.PublicURL;
|
||||||
if (PublicURL === '') {
|
if (PublicURL === '') {
|
||||||
PublicURL = URL.split(':')[0];
|
PublicURL = URL.split(':')[0];
|
||||||
}
|
}
|
||||||
var TLS = $scope.formValues.TLS;
|
|
||||||
var TLSCAFile = $scope.formValues.TLSCACert;
|
var securityData = $scope.formValues.SecurityFormData;
|
||||||
var TLSCertFile = $scope.formValues.TLSCert;
|
var TLS = securityData.TLS;
|
||||||
var TLSKeyFile = $scope.formValues.TLSKey;
|
var TLSMode = securityData.TLSMode;
|
||||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile, false).then(function success(data) {
|
var TLSSkipVerify = TLS && (TLSMode === 'tls_client_noca' || TLSMode === 'tls_only');
|
||||||
|
var TLSSkipClientVerify = TLS && (TLSMode === 'tls_ca' || TLSMode === 'tls_only');
|
||||||
|
var TLSCAFile = TLSSkipVerify ? null : securityData.TLSCACert;
|
||||||
|
var TLSCertFile = TLSSkipClientVerify ? null : securityData.TLSCert;
|
||||||
|
var TLSKeyFile = TLSSkipClientVerify ? null : securityData.TLSKey;
|
||||||
|
|
||||||
|
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile).then(function success(data) {
|
||||||
Notifications.success('Endpoint created', name);
|
Notifications.success('Endpoint created', name);
|
||||||
$state.reload();
|
$state.reload();
|
||||||
}, function error(err) {
|
}, function error(err) {
|
||||||
$scope.state.uploadInProgress = false;
|
$scope.state.uploadInProgress = false;
|
||||||
$scope.state.error = err.msg;
|
Notifications.error('Failure', err, 'Unable to create endpoint');
|
||||||
}, function update(evt) {
|
}, function update(evt) {
|
||||||
if (evt.upload) {
|
if (evt.upload) {
|
||||||
$scope.state.uploadInProgress = evt.upload;
|
$scope.state.uploadInProgress = evt.upload;
|
||||||
|
|
|
@ -23,19 +23,18 @@ function ($scope, $state, $sanitize, Notifications, Authentication, StateManager
|
||||||
return EndpointService.endpoints();
|
return EndpointService.endpoints();
|
||||||
})
|
})
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var endpoints = data;
|
if (data.length === 0) {
|
||||||
if (endpoints.length > 0) {
|
$state.go('init.endpoint');
|
||||||
var endpoint = endpoints[0];
|
} else {
|
||||||
EndpointProvider.setEndpointID(endpoint.Id);
|
var endpointID = data[0].Id;
|
||||||
StateManager.updateEndpointState(true)
|
EndpointProvider.setEndpointID(endpointID);
|
||||||
.then(function success(data) {
|
StateManager.updateEndpointState(false)
|
||||||
|
.then(function success() {
|
||||||
$state.go('dashboard');
|
$state.go('dashboard');
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
Notifications.error('Failure', err, 'Unable to connect to Docker environment');
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
$state.go('init.endpoint');
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
|
|
|
@ -107,12 +107,41 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !tls-checkbox -->
|
<!-- !tls-checkbox -->
|
||||||
<!-- tls-certs -->
|
<!-- tls-options -->
|
||||||
<div ng-if="formValues.TLS">
|
<div ng-if="formValues.TLS">
|
||||||
<!-- ca-input -->
|
<!-- skip-server-verification -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label text-left">TLS CA certificate</label>
|
<div class="col-sm-10">
|
||||||
<div class="col-sm-9">
|
<label for="tls_verify" class="control-label text-left">
|
||||||
|
Skip server verification
|
||||||
|
<portainer-tooltip position="bottom" message="Enable this option if you need to authenticate server based on given CA."></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" ng-model="formValues.TLSSkipVerify"><i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !skip-server-verification -->
|
||||||
|
<!-- skip-client-verification -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label for="tls_client_cert" class="control-label text-left">
|
||||||
|
Skip client verification
|
||||||
|
<portainer-tooltip position="bottom" message="Enable this option if you need to authenticate with a client certificate."></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" ng-model="formValues.TLSSKipClientVerify"><i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !skip-client-verification -->
|
||||||
|
<div class="col-sm-12 form-section-title" ng-if="!formValues.TLSSkipVerify || !formValues.TLSSKipClientVerify">
|
||||||
|
Required TLS files
|
||||||
|
</div>
|
||||||
|
<!-- ca-input -->
|
||||||
|
<div class="form-group" ng-if="!formValues.TLSSkipVerify">
|
||||||
|
<label class="col-sm-4 col-lg-3 control-label text-left">TLS CA certificate</label>
|
||||||
|
<div class="col-sm-8 col-lg-9">
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
|
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCACert">Select file</button>
|
||||||
<span style="margin-left: 5px;">
|
<span style="margin-left: 5px;">
|
||||||
{{ formValues.TLSCACert.name }}
|
{{ formValues.TLSCACert.name }}
|
||||||
|
@ -122,10 +151,11 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- !ca-input -->
|
<!-- !ca-input -->
|
||||||
|
<div ng-if="!formValues.TLSSKipClientVerify">
|
||||||
<!-- cert-input -->
|
<!-- cert-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="tls_cert" class="col-sm-3 control-label text-left">TLS certificate</label>
|
<label for="tls_cert" class="col-sm-4 col-lg-3 control-label text-left">TLS certificate</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-8 col-lg-9">
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCert">Select file</button>
|
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSCert">Select file</button>
|
||||||
<span style="margin-left: 5px;">
|
<span style="margin-left: 5px;">
|
||||||
{{ formValues.TLSCert.name }}
|
{{ formValues.TLSCert.name }}
|
||||||
|
@ -137,8 +167,8 @@
|
||||||
<!-- !cert-input -->
|
<!-- !cert-input -->
|
||||||
<!-- key-input -->
|
<!-- key-input -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="col-sm-3 control-label text-left">TLS key</label>
|
<label class="col-sm-4 col-lg-3 control-label text-left">TLS key</label>
|
||||||
<div class="col-sm-9">
|
<div class="col-sm-8 col-lg-9">
|
||||||
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSKey">Select file</button>
|
<button class="btn btn-sm btn-primary" ngf-select ng-model="formValues.TLSKey">Select file</button>
|
||||||
<span style="margin-left: 5px;">
|
<span style="margin-left: 5px;">
|
||||||
{{ formValues.TLSKey.name }}
|
{{ formValues.TLSKey.name }}
|
||||||
|
@ -149,11 +179,12 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- !key-input -->
|
<!-- !key-input -->
|
||||||
</div>
|
</div>
|
||||||
<!-- !tls-certs -->
|
</div>
|
||||||
|
<!-- !tls-options -->
|
||||||
<!-- actions -->
|
<!-- actions -->
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<div class="col-sm-12">
|
<div class="col-sm-12">
|
||||||
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!formValues.Name || !formValues.URL || (formValues.TLS && (!formValues.TLSCACert || !formValues.TLSCert || !formValues.TLSKey))" ng-click="createRemoteEndpoint()"><i class="fa fa-plug" aria-hidden="true"></i> Connect</button>
|
<button type="submit" class="btn btn-primary btn-sm" ng-disabled="!formValues.Name || !formValues.URL || (formValues.TLS && ((formValues.TLSVerify && !formValues.TLSCACert) || (!formValues.TLSSKipClientVerify && (!formValues.TLSCert || !formValues.TLSKey))))" ng-click="createRemoteEndpoint()"><i class="fa fa-plug" aria-hidden="true"></i> Connect</button>
|
||||||
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
<i id="createResourceSpinner" class="fa fa-cog fa-spin" style="margin-left: 5px; display: none;"></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,6 +2,10 @@ angular.module('initEndpoint', [])
|
||||||
.controller('InitEndpointController', ['$scope', '$state', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications',
|
.controller('InitEndpointController', ['$scope', '$state', 'EndpointService', 'StateManager', 'EndpointProvider', 'Notifications',
|
||||||
function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notifications) {
|
function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notifications) {
|
||||||
|
|
||||||
|
if (!_.isEmpty($scope.applicationState.endpoint)) {
|
||||||
|
$state.go('dashboard');
|
||||||
|
}
|
||||||
|
|
||||||
$scope.logo = StateManager.getState().application.logo;
|
$scope.logo = StateManager.getState().application.logo;
|
||||||
|
|
||||||
$scope.state = {
|
$scope.state = {
|
||||||
|
@ -13,24 +17,22 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
||||||
Name: '',
|
Name: '',
|
||||||
URL: '',
|
URL: '',
|
||||||
TLS: false,
|
TLS: false,
|
||||||
|
TLSSkipVerify: false,
|
||||||
|
TLSSKipClientVerify: false,
|
||||||
TLSCACert: null,
|
TLSCACert: null,
|
||||||
TLSCert: null,
|
TLSCert: null,
|
||||||
TLSKey: null
|
TLSKey: null
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!_.isEmpty($scope.applicationState.endpoint)) {
|
|
||||||
$state.go('dashboard');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
$scope.createLocalEndpoint = function() {
|
$scope.createLocalEndpoint = function() {
|
||||||
$('#createResourceSpinner').show();
|
$('#createResourceSpinner').show();
|
||||||
var name = 'local';
|
var name = 'local';
|
||||||
var URL = 'unix:///var/run/docker.sock';
|
var URL = 'unix:///var/run/docker.sock';
|
||||||
|
|
||||||
|
var endpointID = 1;
|
||||||
EndpointService.createLocalEndpoint(name, URL, false, true)
|
EndpointService.createLocalEndpoint(name, URL, false, true)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var endpointID = data.Id;
|
endpointID = data.Id;
|
||||||
EndpointProvider.setEndpointID(endpointID);
|
EndpointProvider.setEndpointID(endpointID);
|
||||||
return StateManager.updateEndpointState(false);
|
return StateManager.updateEndpointState(false);
|
||||||
})
|
})
|
||||||
|
@ -38,7 +40,8 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
||||||
$state.go('dashboard');
|
$state.go('dashboard');
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
|
||||||
|
EndpointService.deleteEndpoint(endpointID);
|
||||||
})
|
})
|
||||||
.finally(function final() {
|
.finally(function final() {
|
||||||
$('#createResourceSpinner').hide();
|
$('#createResourceSpinner').hide();
|
||||||
|
@ -51,13 +54,16 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
||||||
var URL = $scope.formValues.URL;
|
var URL = $scope.formValues.URL;
|
||||||
var PublicURL = URL.split(':')[0];
|
var PublicURL = URL.split(':')[0];
|
||||||
var TLS = $scope.formValues.TLS;
|
var TLS = $scope.formValues.TLS;
|
||||||
var TLSCAFile = $scope.formValues.TLSCACert;
|
var TLSSkipVerify = TLS && $scope.formValues.TLSSkipVerify;
|
||||||
var TLSCertFile = $scope.formValues.TLSCert;
|
var TLSSKipClientVerify = TLS && $scope.formValues.TLSSKipClientVerify;
|
||||||
var TLSKeyFile = $scope.formValues.TLSKey;
|
var TLSCAFile = TLSSkipVerify ? null : $scope.formValues.TLSCACert;
|
||||||
|
var TLSCertFile = TLSSKipClientVerify ? null : $scope.formValues.TLSCert;
|
||||||
|
var TLSKeyFile = TLSSKipClientVerify ? null : $scope.formValues.TLSKey;
|
||||||
|
|
||||||
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile)
|
var endpointID = 1;
|
||||||
|
EndpointService.createRemoteEndpoint(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSKipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var endpointID = data.Id;
|
endpointID = data.Id;
|
||||||
EndpointProvider.setEndpointID(endpointID);
|
EndpointProvider.setEndpointID(endpointID);
|
||||||
return StateManager.updateEndpointState(false);
|
return StateManager.updateEndpointState(false);
|
||||||
})
|
})
|
||||||
|
@ -65,7 +71,8 @@ function ($scope, $state, EndpointService, StateManager, EndpointProvider, Notif
|
||||||
$state.go('dashboard');
|
$state.go('dashboard');
|
||||||
})
|
})
|
||||||
.catch(function error(err) {
|
.catch(function error(err) {
|
||||||
Notifications.error('Failure', err, 'Unable to connect to the Docker endpoint');
|
Notifications.error('Failure', err, 'Unable to connect to the Docker environment');
|
||||||
|
EndpointService.deleteEndpoint(endpointID);
|
||||||
})
|
})
|
||||||
.finally(function final() {
|
.finally(function final() {
|
||||||
$('#createResourceSpinner').hide();
|
$('#createResourceSpinner').hide();
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
angular.module('portainer').component('porEndpointSecurity', {
|
||||||
|
templateUrl: 'app/directives/endpointSecurity/porEndpointSecurity.html',
|
||||||
|
controller: 'porEndpointSecurityController',
|
||||||
|
bindings: {
|
||||||
|
// This object will be populated with the form data.
|
||||||
|
// Model reference in endpointSecurityModel.js
|
||||||
|
formData: '=',
|
||||||
|
// The component will use this object to initialize the default values
|
||||||
|
// if present.
|
||||||
|
endpoint: '<'
|
||||||
|
}
|
||||||
|
});
|
|
@ -0,0 +1,126 @@
|
||||||
|
<div>
|
||||||
|
<!-- tls-checkbox -->
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<label for="tls" class="control-label text-left">
|
||||||
|
TLS
|
||||||
|
<portainer-tooltip position="bottom" message="Enable this option if you need to connect to the Docker endpoint with TLS."></portainer-tooltip>
|
||||||
|
</label>
|
||||||
|
<label class="switch" style="margin-left: 20px;">
|
||||||
|
<input type="checkbox" ng-model="$ctrl.formData.TLS"><i></i>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !tls-checkbox -->
|
||||||
|
<div class="col-sm-12 form-section-title" ng-if="$ctrl.formData.TLS">
|
||||||
|
TLS mode
|
||||||
|
</div>
|
||||||
|
<!-- note -->
|
||||||
|
<div class="form-group" ng-if="$ctrl.formData.TLS">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
<span class="small text-muted">
|
||||||
|
You can find out more information about how to protect a Docker environment with TLS in the <a href="https://docs.docker.com/engine/security/https/" target="_blank">Docker documentation</a>.
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group"></div>
|
||||||
|
<!-- endpoint-tls-mode -->
|
||||||
|
<div class="form-group" style="margin-bottom: 0" ng-if="$ctrl.formData.TLS">
|
||||||
|
<div class="boxselector_wrapper">
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="tls_client_ca" ng-model="$ctrl.formData.TLSMode" value="tls_client_ca">
|
||||||
|
<label for="tls_client_ca">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
TLS with server and client verification
|
||||||
|
</div>
|
||||||
|
<p>Use client certificates and server verification</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="tls_client_noca" ng-model="$ctrl.formData.TLSMode" value="tls_client_noca">
|
||||||
|
<label for="tls_client_noca">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
TLS with client verification only
|
||||||
|
</div>
|
||||||
|
<p>Use client certificates without server verification</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="tls_ca" ng-model="$ctrl.formData.TLSMode" value="tls_ca">
|
||||||
|
<label for="tls_ca">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
TLS with server verification only
|
||||||
|
</div>
|
||||||
|
<p>Only verify the server certificate</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<input type="radio" id="tls_only" ng-model="$ctrl.formData.TLSMode" value="tls_only">
|
||||||
|
<label for="tls_only">
|
||||||
|
<div class="boxselector_header">
|
||||||
|
<i class="fa fa-shield" aria-hidden="true" style="margin-right: 2px;"></i>
|
||||||
|
TLS only
|
||||||
|
</div>
|
||||||
|
<p>No server/client verification</p>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !endpoint-tls-mode -->
|
||||||
|
<div class="col-sm-12 form-section-title" ng-if="$ctrl.formData.TLS && $ctrl.formData.TLSMode !== 'tls_only'">
|
||||||
|
Required TLS files
|
||||||
|
</div>
|
||||||
|
<!-- tls-file-upload -->
|
||||||
|
<div ng-if="$ctrl.formData.TLS">
|
||||||
|
<!-- tls-file-ca -->
|
||||||
|
<div class="form-group" ng-if="$ctrl.formData.TLSMode === 'tls_client_ca' || $ctrl.formData.TLSMode === 'tls_ca'">
|
||||||
|
<label class="col-sm-3 col-lg-2 control-label text-left">TLS CA certificate</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<button class="btn btn-sm btn-primary" ngf-select ng-model="$ctrl.formData.TLSCACert">Select file</button>
|
||||||
|
<span style="margin-left: 5px;">
|
||||||
|
{{ $ctrl.formData.TLSCACert.name }}
|
||||||
|
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCACert && $ctrl.formData.TLSCACert === $ctrl.endpoint.TLSConfig.TLSCACert" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCACert" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !tls-file-ca -->
|
||||||
|
<!-- tls-files-cert-key -->
|
||||||
|
<div ng-if="$ctrl.formData.TLSMode === 'tls_client_ca' || $ctrl.formData.TLSMode === 'tls_client_noca'">
|
||||||
|
<!-- tls-file-cert -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="tls_cert" class="col-sm-3 col-lg-2 control-label text-left">TLS certificate</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<button class="btn btn-sm btn-primary" ngf-select ng-model="$ctrl.formData.TLSCert">Select file</button>
|
||||||
|
<span style="margin-left: 5px;">
|
||||||
|
{{ $ctrl.formData.TLSCert.name }}
|
||||||
|
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSCert && $ctrl.formData.TLSCert === $ctrl.endpoint.TLSConfig.TLSCert" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSCert" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !tls-file-cert -->
|
||||||
|
<!-- tls-file-key -->
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-3 col-lg-2 control-label text-left">TLS key</label>
|
||||||
|
<div class="col-sm-9 col-lg-10">
|
||||||
|
<button class="btn btn-sm btn-primary" ngf-select ng-model="$ctrl.formData.TLSKey">Select file</button>
|
||||||
|
<span style="margin-left: 5px;">
|
||||||
|
{{ $ctrl.formData.TLSKey.name }}
|
||||||
|
<i class="fa fa-check green-icon" ng-if="$ctrl.formData.TLSKey && $ctrl.formData.TLSKey === $ctrl.endpoint.TLSConfig.TLSKey" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-times red-icon" ng-if="!$ctrl.formData.TLSKey" aria-hidden="true"></i>
|
||||||
|
<i class="fa fa-circle-o-notch fa-spin" ng-if="state.uploadInProgress"></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- !tls-file-key -->
|
||||||
|
</div>
|
||||||
|
<!-- tls-files-cert-key -->
|
||||||
|
</div>
|
||||||
|
<!-- !tls-file-upload -->
|
||||||
|
</div>
|
|
@ -0,0 +1,32 @@
|
||||||
|
angular.module('portainer')
|
||||||
|
.controller('porEndpointSecurityController', [function () {
|
||||||
|
var ctrl = this;
|
||||||
|
|
||||||
|
function initComponent() {
|
||||||
|
if (ctrl.endpoint) {
|
||||||
|
var endpoint = ctrl.endpoint;
|
||||||
|
var TLS = endpoint.TLSConfig.TLS;
|
||||||
|
ctrl.formData.TLS = TLS;
|
||||||
|
var CACert = endpoint.TLSConfig.TLSCACert;
|
||||||
|
ctrl.formData.TLSCACert = CACert;
|
||||||
|
var cert = endpoint.TLSConfig.TLSCert;
|
||||||
|
ctrl.formData.TLSCert = cert;
|
||||||
|
var key = endpoint.TLSConfig.TLSKey;
|
||||||
|
ctrl.formData.TLSKey = key;
|
||||||
|
|
||||||
|
if (TLS) {
|
||||||
|
if (CACert && cert && key) {
|
||||||
|
ctrl.formData.TLSMode = 'tls_client_ca';
|
||||||
|
} else if (cert && key) {
|
||||||
|
ctrl.formData.TLSMode = 'tls_client_noca';
|
||||||
|
} else if (CACert) {
|
||||||
|
ctrl.formData.TLSMode = 'tls_ca';
|
||||||
|
} else {
|
||||||
|
ctrl.formData.TLSMode = 'tls_only';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initComponent();
|
||||||
|
}]);
|
|
@ -0,0 +1,7 @@
|
||||||
|
function EndpointSecurityFormData() {
|
||||||
|
this.TLS = false;
|
||||||
|
this.TLSMode = 'tls_client_ca';
|
||||||
|
this.TLSCACert = null;
|
||||||
|
this.TLSCert = null;
|
||||||
|
this.TLSKey = null;
|
||||||
|
}
|
|
@ -20,6 +20,8 @@ angular.module('portainer.services')
|
||||||
name: endpointParams.name,
|
name: endpointParams.name,
|
||||||
PublicURL: endpointParams.PublicURL,
|
PublicURL: endpointParams.PublicURL,
|
||||||
TLS: endpointParams.TLS,
|
TLS: endpointParams.TLS,
|
||||||
|
TLSSkipVerify: endpointParams.TLSSkipVerify,
|
||||||
|
TLSSkipClientVerify: endpointParams.TLSSkipClientVerify,
|
||||||
authorizedUsers: endpointParams.authorizedUsers
|
authorizedUsers: endpointParams.authorizedUsers
|
||||||
};
|
};
|
||||||
if (endpointParams.type && endpointParams.URL) {
|
if (endpointParams.type && endpointParams.URL) {
|
||||||
|
@ -55,18 +57,20 @@ angular.module('portainer.services')
|
||||||
return Endpoints.create({}, endpoint).$promise;
|
return Endpoints.create({}, endpoint).$promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
service.createRemoteEndpoint = function(name, URL, PublicURL, TLS, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
service.createRemoteEndpoint = function(name, URL, PublicURL, TLS, TLSSkipVerify, TLSSkipClientVerify, TLSCAFile, TLSCertFile, TLSKeyFile) {
|
||||||
var endpoint = {
|
var endpoint = {
|
||||||
Name: name,
|
Name: name,
|
||||||
URL: 'tcp://' + URL,
|
URL: 'tcp://' + URL,
|
||||||
PublicURL: PublicURL,
|
PublicURL: PublicURL,
|
||||||
TLS: TLS
|
TLS: TLS,
|
||||||
|
TLSSkipVerify: TLSSkipVerify,
|
||||||
|
TLSSkipClientVerify: TLSSkipClientVerify
|
||||||
};
|
};
|
||||||
var deferred = $q.defer();
|
var deferred = $q.defer();
|
||||||
Endpoints.create({}, endpoint).$promise
|
Endpoints.create({}, endpoint).$promise
|
||||||
.then(function success(data) {
|
.then(function success(data) {
|
||||||
var endpointID = data.Id;
|
var endpointID = data.Id;
|
||||||
if (TLS) {
|
if (!TLSSkipVerify || !TLSSkipClientVerify) {
|
||||||
deferred.notify({upload: true});
|
deferred.notify({upload: true});
|
||||||
FileUploadService.uploadTLSFilesForEndpoint(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile)
|
FileUploadService.uploadTLSFilesForEndpoint(endpointID, TLSCAFile, TLSCertFile, TLSKeyFile)
|
||||||
.then(function success() {
|
.then(function success() {
|
||||||
|
|
Loading…
Reference in New Issue