diff --git a/.codefresh/codefresh_develop.yml b/.codefresh/codefresh_branch.yml similarity index 95% rename from .codefresh/codefresh_develop.yml rename to .codefresh/codefresh_branch.yml index 7a17aee3e..4c5fb1222 100644 --- a/.codefresh/codefresh_develop.yml +++ b/.codefresh/codefresh_branch.yml @@ -44,7 +44,3 @@ steps: candidate: '${{build_image}}' tag: '${{CF_BRANCH}}' registry: dockerhub - when: - branch: - only: - - develop diff --git a/api/bolt/migrate_dbversion10.go b/api/bolt/migrate_dbversion10.go new file mode 100644 index 000000000..211d2497a --- /dev/null +++ b/api/bolt/migrate_dbversion10.go @@ -0,0 +1,28 @@ +package bolt + +import "github.com/portainer/portainer" + +func (m *Migrator) updateEndpointsToVersion11() error { + legacyEndpoints, err := m.EndpointService.Endpoints() + if err != nil { + return err + } + + for _, endpoint := range legacyEndpoints { + if endpoint.Type == portainer.AgentOnDockerEnvironment { + endpoint.TLSConfig.TLS = true + endpoint.TLSConfig.TLSSkipVerify = true + } else { + if endpoint.TLSConfig.TLSSkipVerify && !endpoint.TLSConfig.TLS { + endpoint.TLSConfig.TLSSkipVerify = false + } + } + + err = m.EndpointService.UpdateEndpoint(endpoint.ID, &endpoint) + if err != nil { + return err + } + } + + return nil +} diff --git a/api/bolt/migrator.go b/api/bolt/migrator.go index 66566ae8d..48242adaf 100644 --- a/api/bolt/migrator.go +++ b/api/bolt/migrator.go @@ -112,6 +112,14 @@ func (m *Migrator) Migrate() error { } } + // https://github.com/portainer/portainer/issues/1906 + if m.CurrentDBVersion < 11 { + err := m.updateEndpointsToVersion11() + if err != nil { + return err + } + } + err := m.VersionService.StoreDBVersion(portainer.DBVersion) if err != nil { return err diff --git a/api/cli/cli.go b/api/cli/cli.go index f2307718b..b9e00ca66 100644 --- a/api/cli/cli.go +++ b/api/cli/cli.go @@ -33,11 +33,11 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) { Addr: kingpin.Flag("bind", "Address and port to serve Portainer").Default(defaultBindAddress).Short('p').String(), Assets: kingpin.Flag("assets", "Path to the assets").Default(defaultAssetsDirectory).Short('a').String(), Data: kingpin.Flag("data", "Path to the folder where the data is stored").Default(defaultDataDirectory).Short('d').String(), - Endpoint: kingpin.Flag("host", "Dockerd endpoint").Short('H').String(), + EndpointURL: kingpin.Flag("host", "Endpoint URL").Short('H').String(), ExternalEndpoints: kingpin.Flag("external-endpoints", "Path to a file defining available endpoints").String(), NoAuth: kingpin.Flag("no-auth", "Disable authentication").Default(defaultNoAuth).Bool(), NoAnalytics: kingpin.Flag("no-analytics", "Disable Analytics in app").Default(defaultNoAnalytics).Bool(), - TLSVerify: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLSVerify).Bool(), + TLS: kingpin.Flag("tlsverify", "TLS support").Default(defaultTLS).Bool(), TLSSkipVerify: kingpin.Flag("tlsskipverify", "Disable TLS server verification").Default(defaultTLSSkipVerify).Bool(), TLSCacert: kingpin.Flag("tlscacert", "Path to the CA").Default(defaultTLSCACertPath).String(), TLSCert: kingpin.Flag("tlscert", "Path to the TLS certificate file").Default(defaultTLSCertPath).String(), @@ -69,11 +69,11 @@ func (*Service) ParseFlags(version string) (*portainer.CLIFlags, error) { // ValidateFlags validates the values of the flags. func (*Service) ValidateFlags(flags *portainer.CLIFlags) error { - if *flags.Endpoint != "" && *flags.ExternalEndpoints != "" { + if *flags.EndpointURL != "" && *flags.ExternalEndpoints != "" { return errEndpointExcludeExternal } - err := validateEndpoint(*flags.Endpoint) + err := validateEndpointURL(*flags.EndpointURL) if err != nil { return err } @@ -99,14 +99,14 @@ func (*Service) ValidateFlags(flags *portainer.CLIFlags) error { return nil } -func validateEndpoint(endpoint string) error { - if endpoint != "" { - if !strings.HasPrefix(endpoint, "unix://") && !strings.HasPrefix(endpoint, "tcp://") { +func validateEndpointURL(endpointURL string) error { + if endpointURL != "" { + if !strings.HasPrefix(endpointURL, "unix://") && !strings.HasPrefix(endpointURL, "tcp://") { return errInvalidEndpointProtocol } - if strings.HasPrefix(endpoint, "unix://") { - socketPath := strings.TrimPrefix(endpoint, "unix://") + if strings.HasPrefix(endpointURL, "unix://") { + socketPath := strings.TrimPrefix(endpointURL, "unix://") if _, err := os.Stat(socketPath); err != nil { if os.IsNotExist(err) { return errSocketNotFound diff --git a/api/cli/defaults.go b/api/cli/defaults.go index b2e40c969..419e5fd81 100644 --- a/api/cli/defaults.go +++ b/api/cli/defaults.go @@ -8,7 +8,7 @@ const ( defaultAssetsDirectory = "./" defaultNoAuth = "false" defaultNoAnalytics = "false" - defaultTLSVerify = "false" + defaultTLS = "false" defaultTLSSkipVerify = "false" defaultTLSCACertPath = "/certs/ca.pem" defaultTLSCertPath = "/certs/cert.pem" diff --git a/api/cli/defaults_windows.go b/api/cli/defaults_windows.go index 6fead127e..2bd909c22 100644 --- a/api/cli/defaults_windows.go +++ b/api/cli/defaults_windows.go @@ -6,7 +6,7 @@ const ( defaultAssetsDirectory = "./" defaultNoAuth = "false" defaultNoAnalytics = "false" - defaultTLSVerify = "false" + defaultTLS = "false" defaultTLSSkipVerify = "false" defaultTLSCACertPath = "C:\\certs\\ca.pem" defaultTLSCertPath = "C:\\certs\\cert.pem" diff --git a/api/cmd/portainer/main.go b/api/cmd/portainer/main.go index 0e30cf276..3c2dba7c6 100644 --- a/api/cmd/portainer/main.go +++ b/api/cmd/portainer/main.go @@ -1,6 +1,8 @@ package main // import "github.com/portainer/portainer" import ( + "strings" + "github.com/portainer/portainer" "github.com/portainer/portainer/bolt" "github.com/portainer/portainer/cli" @@ -62,8 +64,8 @@ func initStore(dataStorePath string) *bolt.Store { return store } -func initStackManager(assetsPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService) (portainer.StackManager, error) { - return exec.NewStackManager(assetsPath, signatureService, fileService) +func initStackManager(assetsPath string, dataStorePath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService) (portainer.StackManager, error) { + return exec.NewStackManager(assetsPath, dataStorePath, signatureService, fileService) } func initJWTService(authenticationEnabled bool) portainer.JWTService { @@ -207,6 +209,93 @@ func initKeyPair(fileService portainer.FileService, signatureService portainer.D return generateAndStoreKeyPair(fileService, signatureService) } +func createTLSSecuredEndpoint(flags *portainer.CLIFlags, endpointService portainer.EndpointService) error { + tlsConfiguration := portainer.TLSConfiguration{ + TLS: *flags.TLS, + TLSSkipVerify: *flags.TLSSkipVerify, + } + + if *flags.TLS { + tlsConfiguration.TLSCACertPath = *flags.TLSCacert + tlsConfiguration.TLSCertPath = *flags.TLSCert + tlsConfiguration.TLSKeyPath = *flags.TLSKey + } else if !*flags.TLS && *flags.TLSSkipVerify { + tlsConfiguration.TLS = true + } + + endpoint := &portainer.Endpoint{ + Name: "primary", + URL: *flags.EndpointURL, + GroupID: portainer.EndpointGroupID(1), + Type: portainer.DockerEnvironment, + TLSConfig: tlsConfiguration, + AuthorizedUsers: []portainer.UserID{}, + AuthorizedTeams: []portainer.TeamID{}, + Extensions: []portainer.EndpointExtension{}, + } + + if strings.HasPrefix(endpoint.URL, "tcp://") { + tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(tlsConfiguration.TLSCACertPath, tlsConfiguration.TLSCertPath, tlsConfiguration.TLSKeyPath, tlsConfiguration.TLSSkipVerify) + if err != nil { + return err + } + + agentOnDockerEnvironment, err := client.ExecutePingOperation(endpoint.URL, tlsConfig) + if err != nil { + return err + } + + if agentOnDockerEnvironment { + endpoint.Type = portainer.AgentOnDockerEnvironment + } + } + + return endpointService.CreateEndpoint(endpoint) +} + +func createUnsecuredEndpoint(endpointURL string, endpointService portainer.EndpointService) error { + if strings.HasPrefix(endpointURL, "tcp://") { + _, err := client.ExecutePingOperation(endpointURL, nil) + if err != nil { + return err + } + } + + endpoint := &portainer.Endpoint{ + Name: "primary", + URL: endpointURL, + GroupID: portainer.EndpointGroupID(1), + Type: portainer.DockerEnvironment, + TLSConfig: portainer.TLSConfiguration{}, + AuthorizedUsers: []portainer.UserID{}, + AuthorizedTeams: []portainer.TeamID{}, + Extensions: []portainer.EndpointExtension{}, + } + + return endpointService.CreateEndpoint(endpoint) +} + +func initEndpoint(flags *portainer.CLIFlags, endpointService portainer.EndpointService) error { + if *flags.EndpointURL == "" { + return nil + } + + endpoints, err := endpointService.Endpoints() + if err != nil { + return err + } + + if len(endpoints) > 0 { + log.Println("Instance already has defined endpoints. Skipping the endpoint defined via CLI.") + return nil + } + + if *flags.TLS || *flags.TLSSkipVerify { + return createTLSSecuredEndpoint(flags, endpointService) + } + return createUnsecuredEndpoint(*flags.EndpointURL, endpointService) +} + func main() { flags := initCLI() @@ -232,7 +321,7 @@ func main() { log.Fatal(err) } - stackManager, err := initStackManager(*flags.Assets, digitalSignatureService, fileService) + stackManager, err := initStackManager(*flags.Assets, *flags.Data, digitalSignatureService, fileService) if err != nil { log.Fatal(err) } @@ -249,44 +338,9 @@ func main() { applicationStatus := initStatus(authorizeEndpointMgmt, flags) - if *flags.Endpoint != "" { - endpoints, err := store.EndpointService.Endpoints() - if err != nil { - log.Fatal(err) - } - if len(endpoints) == 0 { - endpoint := &portainer.Endpoint{ - Name: "primary", - URL: *flags.Endpoint, - Type: portainer.DockerEnvironment, - TLSConfig: portainer.TLSConfiguration{ - TLS: *flags.TLSVerify, - TLSSkipVerify: *flags.TLSSkipVerify, - TLSCACertPath: *flags.TLSCacert, - TLSCertPath: *flags.TLSCert, - TLSKeyPath: *flags.TLSKey, - }, - AuthorizedUsers: []portainer.UserID{}, - AuthorizedTeams: []portainer.TeamID{}, - Extensions: []portainer.EndpointExtension{}, - } - - agentOnDockerEnvironment, err := client.ExecutePingOperationFromEndpoint(endpoint) - if err != nil { - log.Fatal(err) - } - - if agentOnDockerEnvironment { - endpoint.Type = portainer.AgentOnDockerEnvironment - } - - err = store.EndpointService.CreateEndpoint(endpoint) - if err != nil { - log.Fatal(err) - } - } else { - log.Println("Instance already has defined endpoints. Skipping the endpoint defined via CLI.") - } + err = initEndpoint(flags, store.EndpointService) + if err != nil { + log.Fatal(err) } adminPasswordHash := "" diff --git a/api/crypto/tls.go b/api/crypto/tls.go index ae6bac867..641aed142 100644 --- a/api/crypto/tls.go +++ b/api/crypto/tls.go @@ -4,11 +4,11 @@ import ( "crypto/tls" "crypto/x509" "io/ioutil" - - "github.com/portainer/portainer" ) -func CreateTLSConfig(caCert, cert, key []byte, skipClientVerification, skipServerVerification bool) (*tls.Config, error) { +// CreateTLSConfigurationFromBytes initializes a tls.Config using a CA certificate, a certificate and a key +// loaded from memory. +func CreateTLSConfigurationFromBytes(caCert, cert, key []byte, skipClientVerification, skipServerVerification bool) (*tls.Config, error) { config := &tls.Config{} config.InsecureSkipVerify = skipServerVerification @@ -29,32 +29,31 @@ func CreateTLSConfig(caCert, cert, key []byte, skipClientVerification, skipServe return config, nil } -// CreateTLSConfiguration initializes a tls.Config using a CA certificate, a certificate and a key -func CreateTLSConfiguration(config *portainer.TLSConfiguration) (*tls.Config, error) { - TLSConfig := &tls.Config{} +// CreateTLSConfigurationFromDisk initializes a tls.Config using a CA certificate, a certificate and a key +// loaded from disk. +func CreateTLSConfigurationFromDisk(caCertPath, certPath, keyPath string, skipServerVerification bool) (*tls.Config, error) { + config := &tls.Config{} + config.InsecureSkipVerify = skipServerVerification - if config.TLS && config.TLSCertPath != "" && config.TLSKeyPath != "" { - cert, err := tls.LoadX509KeyPair(config.TLSCertPath, config.TLSKeyPath) + if certPath != "" && keyPath != "" { + cert, err := tls.LoadX509KeyPair(certPath, keyPath) if err != nil { return nil, err } - TLSConfig.Certificates = []tls.Certificate{cert} + config.Certificates = []tls.Certificate{cert} } - if config.TLS && !config.TLSSkipVerify { - caCert, err := ioutil.ReadFile(config.TLSCACertPath) + if !skipServerVerification && caCertPath != "" { + caCert, err := ioutil.ReadFile(caCertPath) if err != nil { return nil, err } caCertPool := x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) - - TLSConfig.RootCAs = caCertPool + config.RootCAs = caCertPool } - TLSConfig.InsecureSkipVerify = config.TLSSkipVerify - - return TLSConfig, nil + return config, nil } diff --git a/api/exec/stack_manager.go b/api/exec/stack_manager.go index d8da84df1..d8c2d9438 100644 --- a/api/exec/stack_manager.go +++ b/api/exec/stack_manager.go @@ -2,6 +2,7 @@ package exec import ( "bytes" + "encoding/json" "os" "os/exec" "path" @@ -13,28 +14,22 @@ import ( // StackManager represents a service for managing stacks. type StackManager struct { binaryPath string + dataPath string signatureService portainer.DigitalSignatureService fileService portainer.FileService } -type dockerCLIConfiguration struct { - HTTPHeaders struct { - ManagerOperationHeader string `json:"X-PortainerAgent-ManagerOperation"` - SignatureHeader string `json:"X-PortainerAgent-Signature"` - PublicKey string `json:"X-PortainerAgent-PublicKey"` - } `json:"HttpHeaders"` -} - // NewStackManager initializes a new StackManager service. // It also updates the configuration of the Docker CLI binary. -func NewStackManager(binaryPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService) (*StackManager, error) { +func NewStackManager(binaryPath, dataPath string, signatureService portainer.DigitalSignatureService, fileService portainer.FileService) (*StackManager, error) { manager := &StackManager{ binaryPath: binaryPath, + dataPath: dataPath, signatureService: signatureService, fileService: fileService, } - err := manager.updateDockerCLIConfiguration(binaryPath) + err := manager.updateDockerCLIConfiguration(dataPath) if err != nil { return nil, err } @@ -44,7 +39,7 @@ func NewStackManager(binaryPath string, signatureService portainer.DigitalSignat // Login executes the docker login command against a list of registries (including DockerHub). func (manager *StackManager) Login(dockerhub *portainer.DockerHub, registries []portainer.Registry, endpoint *portainer.Endpoint) { - command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint) + command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint) for _, registry := range registries { if registry.Authentication { registryArgs := append(args, "login", "--username", registry.Username, "--password", registry.Password, registry.URL) @@ -60,7 +55,7 @@ func (manager *StackManager) Login(dockerhub *portainer.DockerHub, registries [] // Logout executes the docker logout command. func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error { - command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint) + command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint) args = append(args, "logout") return runCommandAndCaptureStdErr(command, args, nil, "") } @@ -68,7 +63,7 @@ func (manager *StackManager) Logout(endpoint *portainer.Endpoint) error { // Deploy executes the docker stack deploy command. func (manager *StackManager) Deploy(stack *portainer.Stack, prune bool, endpoint *portainer.Endpoint) error { stackFilePath := path.Join(stack.ProjectPath, stack.EntryPoint) - command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint) + command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint) if prune { args = append(args, "stack", "deploy", "--prune", "--with-registry-auth", "--compose-file", stackFilePath, stack.Name) @@ -87,7 +82,7 @@ func (manager *StackManager) Deploy(stack *portainer.Stack, prune bool, endpoint // Remove executes the docker stack rm command. func (manager *StackManager) Remove(stack *portainer.Stack, endpoint *portainer.Endpoint) error { - command, args := prepareDockerCommandAndArgs(manager.binaryPath, endpoint) + command, args := prepareDockerCommandAndArgs(manager.binaryPath, manager.dataPath, endpoint) args = append(args, "stack", "rm", stack.Name) return runCommandAndCaptureStdErr(command, args, nil, "") } @@ -111,7 +106,7 @@ func runCommandAndCaptureStdErr(command string, args []string, env []string, wor return nil } -func prepareDockerCommandAndArgs(binaryPath string, endpoint *portainer.Endpoint) (string, []string) { +func prepareDockerCommandAndArgs(binaryPath, dataPath string, endpoint *portainer.Endpoint) (string, []string) { // Assume Linux as a default command := path.Join(binaryPath, "docker") @@ -120,12 +115,10 @@ func prepareDockerCommandAndArgs(binaryPath string, endpoint *portainer.Endpoint } args := make([]string, 0) - args = append(args, "--config", binaryPath) + args = append(args, "--config", dataPath) args = append(args, "-H", endpoint.URL) - if !endpoint.TLSConfig.TLS && endpoint.TLSConfig.TLSSkipVerify { - args = append(args, "--tls") - } else if endpoint.TLSConfig.TLS { + if endpoint.TLSConfig.TLS { args = append(args, "--tls") if !endpoint.TLSConfig.TLSSkipVerify { @@ -140,21 +133,46 @@ func prepareDockerCommandAndArgs(binaryPath string, endpoint *portainer.Endpoint return command, args } -func (manager *StackManager) updateDockerCLIConfiguration(binaryPath string) error { - config := dockerCLIConfiguration{} - config.HTTPHeaders.ManagerOperationHeader = "1" +func (manager *StackManager) updateDockerCLIConfiguration(dataPath string) error { + configFilePath := path.Join(dataPath, "config.json") + config, err := manager.retrieveConfigurationFromDisk(configFilePath) + if err != nil { + return err + } signature, err := manager.signatureService.Sign(portainer.PortainerAgentSignatureMessage) if err != nil { return err } - config.HTTPHeaders.SignatureHeader = signature - config.HTTPHeaders.PublicKey = manager.signatureService.EncodedPublicKey() - err = manager.fileService.WriteJSONToFile(path.Join(binaryPath, "config.json"), config) + if config["HttpHeaders"] == nil { + config["HttpHeaders"] = make(map[string]interface{}) + } + headersObject := config["HttpHeaders"].(map[string]interface{}) + headersObject["X-PortainerAgent-ManagerOperation"] = "1" + headersObject["X-PortainerAgent-Signature"] = signature + headersObject["X-PortainerAgent-PublicKey"] = manager.signatureService.EncodedPublicKey() + + err = manager.fileService.WriteJSONToFile(configFilePath, config) if err != nil { return err } return nil } + +func (manager *StackManager) retrieveConfigurationFromDisk(path string) (map[string]interface{}, error) { + var config map[string]interface{} + + raw, err := manager.fileService.GetFileContent(path) + if err != nil { + return make(map[string]interface{}), nil + } + + err = json.Unmarshal([]byte(raw), &config) + if err != nil { + return nil, err + } + + return config, nil +} diff --git a/api/http/client/client.go b/api/http/client/client.go index b9e3f318b..438be12ad 100644 --- a/api/http/client/client.go +++ b/api/http/client/client.go @@ -7,39 +7,8 @@ import ( "time" "github.com/portainer/portainer" - "github.com/portainer/portainer/crypto" ) -// ExecutePingOperationFromEndpoint will send a SystemPing operation HTTP request to a Docker environment -// using the specified endpoint configuration. It is used exclusively when -// specifying an endpoint from the CLI via the -H flag. -func ExecutePingOperationFromEndpoint(endpoint *portainer.Endpoint) (bool, error) { - if strings.HasPrefix(endpoint.URL, "unix://") { - return false, nil - } - - transport := &http.Transport{} - - scheme := "http" - - if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify { - tlsConfig, err := crypto.CreateTLSConfiguration(&endpoint.TLSConfig) - if err != nil { - return false, err - } - scheme = "https" - transport.TLSClientConfig = tlsConfig - } - - client := &http.Client{ - Timeout: time.Second * 3, - Transport: transport, - } - - target := strings.Replace(endpoint.URL, "tcp://", scheme+"://", 1) - return pingOperation(client, target) -} - // ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment // using the specified host and optional TLS configuration. func ExecutePingOperation(host string, tlsConfig *tls.Config) (bool, error) { diff --git a/api/http/handler/endpoint.go b/api/http/handler/endpoint.go index 141f8d29d..23c867303 100644 --- a/api/http/handler/endpoint.go +++ b/api/http/handler/endpoint.go @@ -121,7 +121,7 @@ func (handler *EndpointHandler) handleGetEndpoints(w http.ResponseWriter, r *htt } func (handler *EndpointHandler) createTLSSecuredEndpoint(payload *postEndpointPayload) (*portainer.Endpoint, error) { - tlsConfig, err := crypto.CreateTLSConfig(payload.caCert, payload.cert, payload.key, payload.skipTLSClientVerification, payload.skipTLSServerVerification) + tlsConfig, err := crypto.CreateTLSConfigurationFromBytes(payload.caCert, payload.cert, payload.key, payload.skipTLSClientVerification, payload.skipTLSServerVerification) if err != nil { return nil, err } @@ -472,7 +472,7 @@ func (handler *EndpointHandler) handlePutEndpoint(w http.ResponseWriter, r *http } } else { endpoint.TLSConfig.TLS = false - endpoint.TLSConfig.TLSSkipVerify = true + endpoint.TLSConfig.TLSSkipVerify = false endpoint.TLSConfig.TLSCACertPath = "" endpoint.TLSConfig.TLSCertPath = "" endpoint.TLSConfig.TLSKeyPath = "" diff --git a/api/http/handler/websocket.go b/api/http/handler/websocket.go index 3e624d3b4..629e60390 100644 --- a/api/http/handler/websocket.go +++ b/api/http/handler/websocket.go @@ -94,6 +94,8 @@ func (handler *WebSocketHandler) handleWebsocketExec(w http.ResponseWriter, r *h } func (handler *WebSocketHandler) handleRequest(w http.ResponseWriter, r *http.Request, params *webSocketExecRequestParams) error { + r.Header.Del("Origin") + if params.nodeName != "" { return handler.proxyWebsocketRequest(w, r, params) } @@ -135,7 +137,6 @@ func (handler *WebSocketHandler) proxyWebsocketRequest(w http.ResponseWriter, r out.Set(portainer.PortainerAgentTargetHeader, params.nodeName) } - r.Header.Del("Origin") proxy.ServeHTTP(w, r) return nil @@ -187,7 +188,7 @@ func createDial(endpoint *portainer.Endpoint) (net.Conn, error) { } if endpoint.TLSConfig.TLS { - tlsConfig, err := crypto.CreateTLSConfiguration(&endpoint.TLSConfig) + tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify) if err != nil { return nil, err } diff --git a/api/http/proxy/factory.go b/api/http/proxy/factory.go index 9df363332..8f952f2dc 100644 --- a/api/http/proxy/factory.go +++ b/api/http/proxy/factory.go @@ -29,7 +29,7 @@ func (factory *proxyFactory) newDockerHTTPSProxy(u *url.URL, tlsConfig *portaine u.Scheme = "https" proxy := factory.createDockerReverseProxy(u, enableSignature) - config, err := crypto.CreateTLSConfiguration(tlsConfig) + config, err := crypto.CreateTLSConfigurationFromDisk(tlsConfig.TLSCACertPath, tlsConfig.TLSCertPath, tlsConfig.TLSKeyPath, tlsConfig.TLSSkipVerify) if err != nil { return nil, err } diff --git a/api/http/proxy/manager.go b/api/http/proxy/manager.go index c1906e45b..2a9018102 100644 --- a/api/http/proxy/manager.go +++ b/api/http/proxy/manager.go @@ -60,7 +60,7 @@ func (manager *Manager) CreateAndRegisterProxy(endpoint *portainer.Endpoint) (ht } if endpointURL.Scheme == "tcp" { - if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify { + if endpoint.TLSConfig.TLS { proxy, err = manager.proxyFactory.newDockerHTTPSProxy(endpointURL, &endpoint.TLSConfig, enableSignature) if err != nil { return nil, err diff --git a/api/http/proxy/response.go b/api/http/proxy/response.go index 751e3df31..3be65bbc6 100644 --- a/api/http/proxy/response.go +++ b/api/http/proxy/response.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "io/ioutil" + "log" "net/http" "strconv" @@ -48,8 +49,10 @@ func getResponseAsJSONArray(response *http.Response) ([]interface{}, error) { if responseObject["message"] != nil { return nil, portainer.Error(responseObject["message"].(string)) } + log.Printf("Response: %+v\n", responseObject) return nil, ErrInvalidResponseContent default: + log.Printf("Response: %+v\n", responseObject) return nil, ErrInvalidResponseContent } } diff --git a/api/ldap/ldap.go b/api/ldap/ldap.go index 3ad222d05..7b72b8930 100644 --- a/api/ldap/ldap.go +++ b/api/ldap/ldap.go @@ -55,7 +55,7 @@ func searchUser(username string, conn *ldap.Conn, settings []portainer.LDAPSearc func createConnection(settings *portainer.LDAPSettings) (*ldap.Conn, error) { if settings.TLSConfig.TLS || settings.StartTLS { - config, err := crypto.CreateTLSConfiguration(&settings.TLSConfig) + config, err := crypto.CreateTLSConfigurationFromDisk(settings.TLSConfig.TLSCACertPath, settings.TLSConfig.TLSCertPath, settings.TLSConfig.TLSKeyPath, settings.TLSConfig.TLSSkipVerify) if err != nil { return nil, err } diff --git a/api/portainer.go b/api/portainer.go index c502a5b15..ac9fd0c92 100644 --- a/api/portainer.go +++ b/api/portainer.go @@ -16,14 +16,14 @@ type ( AdminPasswordFile *string Assets *string Data *string - Endpoint *string + EndpointURL *string ExternalEndpoints *string Labels *[]Pair Logo *string NoAuth *bool NoAnalytics *bool Templates *string - TLSVerify *bool + TLS *bool TLSSkipVerify *bool TLSCacert *string TLSCert *string @@ -443,9 +443,9 @@ type ( const ( // APIVersion is the version number of the Portainer API. - APIVersion = "1.17.0" + APIVersion = "1.17.1" // DBVersion is the version number of the Portainer database. - DBVersion = 10 + DBVersion = 11 // DefaultTemplatesURL represents the default URL for the templates definitions. DefaultTemplatesURL = "https://raw.githubusercontent.com/portainer/templates/master/templates.json" // PortainerAgentHeader represents the name of the header available in any agent response diff --git a/api/swagger.yaml b/api/swagger.yaml index fea616110..0f81812f1 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -56,7 +56,7 @@ info: **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.17.0" + version: "1.17.1" title: "Portainer API" contact: email: "info@portainer.io" @@ -228,12 +228,45 @@ paths: produces: - "application/json" parameters: - - in: "body" - name: "body" - description: "Endpoint details" + - name: "Name" + in: "formData" + type: "string" + description: "Name that will be used to identify this endpoint (example: my-endpoint)" required: true - schema: - $ref: "#/definitions/EndpointCreateRequest" + - name: "URL" + in: "formData" + type: "string" + description: "URL or IP address of a Docker host (example: docker.mydomain.tld:2375)" + required: true + - name: "PublicURL" + in: "formData" + type: "string" + description: "URL or IP address where exposed containers will be reachable.\ + \ Defaults to URL if not specified (example: docker.mydomain.tld:2375)" + - name: "GroupID" + in: "formData" + type: "string" + description: "Endpoint group identifier. If not specified will default to 1 (unassigned)." + - name: "TLS" + in: "formData" + type: "string" + description: "Require TLS to connect against this endpoint (example: true)" + - name: "TLSSkipVerify" + in: "formData" + type: "string" + description: "Skip server verification when using TLS" (example: false) + - name: "TLSCACertFile" + in: "formData" + type: "file" + description: "TLS CA certificate file" + - name: "TLSCertFile" + in: "formData" + type: "file" + description: "TLS client certificate file" + - name: "TLSKeyFile" + in: "formData" + type: "file" + description: "TLS client key file" responses: 200: description: "Success" @@ -2143,7 +2176,7 @@ definitions: description: "Is analytics enabled" Version: type: "string" - example: "1.17.0" + example: "1.17.1" description: "Portainer API version" PublicSettingsInspectResponse: type: "object" @@ -2344,10 +2377,22 @@ definitions: type: "string" example: "my-endpoint" description: "Endpoint name" + Type: + type: "integer" + example: 1 + description: "Endpoint environment type. 1 for a Docker environment, 2 for an agent on Docker environment." URL: type: "string" example: "docker.mydomain.tld:2375" description: "URL or IP address of the Docker host associated to this endpoint" + PublicURL: + type: "string" + example: "docker.mydomain.tld:2375" + description: "URL or IP address where exposed containers will be reachable" + GroupID: + type: "integer" + example: 1 + description: "Endpoint group identifier" AuthorizedUsers: type: "array" description: "List of user identifiers authorized to connect to this endpoint" @@ -2424,53 +2469,6 @@ definitions: type: "string" example: "hub_password" description: "Password used to authenticate against the DockerHub" - EndpointCreateRequest: - type: "object" - required: - - "Name" - - "URL" - properties: - Name: - type: "string" - example: "my-endpoint" - description: "Name that will be used to identify this endpoint" - URL: - type: "string" - example: "docker.mydomain.tld:2375" - description: "URL or IP address of a Docker host" - PublicURL: - type: "string" - example: "docker.mydomain.tld:2375" - description: "URL or IP address where exposed containers will be reachable.\ - \ Defaults to URL if not specified" - TLS: - type: "boolean" - example: true - 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" - TLSCACertFile: - type: "file" - description: "TLS CA certificate file" - TLSCertFile: - type: "file" - description: "TLS client certificate file" - TLSKeyFile: - type: "file" - description: "TLS client key file" - EndpointCreateResponse: - type: "object" - properties: - Id: - type: "integer" - example: 1 - description: "Id of the endpoint" EndpointListResponse: type: "array" items: diff --git a/app/docker/components/datatables/services-datatable/servicesDatatable.html b/app/docker/components/datatables/services-datatable/servicesDatatable.html index 04584de28..12794918d 100644 --- a/app/docker/components/datatables/services-datatable/servicesDatatable.html +++ b/app/docker/components/datatables/services-datatable/servicesDatatable.html @@ -108,7 +108,7 @@ - + diff --git a/app/docker/components/datatables/tasks-datatable/tasksDatatable.html b/app/docker/components/datatables/tasks-datatable/tasksDatatable.html index dc151b5ca..abe642d60 100644 --- a/app/docker/components/datatables/tasks-datatable/tasksDatatable.html +++ b/app/docker/components/datatables/tasks-datatable/tasksDatatable.html @@ -60,8 +60,8 @@