mirror of
https://github.com/portainer/portainer.git
synced 2025-08-06 14:25:31 +02:00
fix(tls): centralize the TLS configuration to ensure FIPS compliance BE-11979 (#960)
This commit is contained in:
parent
3eab294908
commit
163aa57e5c
25 changed files with 454 additions and 112 deletions
|
@ -1,7 +1,6 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
|
@ -11,6 +10,7 @@ import (
|
|||
"time"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/segmentio/encoding/json"
|
||||
|
@ -105,21 +105,28 @@ func Get(url string, timeout int) ([]byte, error) {
|
|||
// ExecutePingOperation will send a SystemPing operation HTTP request to a Docker environment(endpoint)
|
||||
// using the specified host and optional TLS configuration.
|
||||
// It uses a new Http.Client for each operation.
|
||||
func ExecutePingOperation(host string, tlsConfig *tls.Config) (bool, error) {
|
||||
func ExecutePingOperation(host string, tlsConfiguration portainer.TLSConfiguration) (bool, error) {
|
||||
transport := &http.Transport{}
|
||||
|
||||
scheme := "http"
|
||||
if tlsConfig != nil {
|
||||
|
||||
if tlsConfiguration.TLS {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(tlsConfiguration)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
transport.TLSClientConfig = tlsConfig
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Timeout: time.Second * 3,
|
||||
Timeout: 3 * time.Second,
|
||||
Transport: transport,
|
||||
}
|
||||
|
||||
target := strings.Replace(host, "tcp://", scheme+"://", 1)
|
||||
|
||||
return pingOperation(client, target)
|
||||
}
|
||||
|
||||
|
|
31
api/http/client/client_test.go
Normal file
31
api/http/client/client_test.go
Normal file
|
@ -0,0 +1,31 @@
|
|||
package client
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExecutePingOperationFailure(t *testing.T) {
|
||||
host := "http://localhost:1"
|
||||
config := portainer.TLSConfiguration{
|
||||
TLS: true,
|
||||
TLSSkipVerify: true,
|
||||
}
|
||||
|
||||
// Invalid host
|
||||
ok, err := ExecutePingOperation(host, config)
|
||||
require.False(t, ok)
|
||||
require.Error(t, err)
|
||||
|
||||
// Invalid TLS configuration
|
||||
config.TLSCertPath = "/invalid/path/to/cert"
|
||||
config.TLSKeyPath = "/invalid/path/to/key"
|
||||
|
||||
ok, err = ExecutePingOperation(host, config)
|
||||
require.False(t, ok)
|
||||
require.Error(t, err)
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package endpoints
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"net/http"
|
||||
"runtime"
|
||||
|
@ -285,8 +284,6 @@ func (handler *Handler) endpointCreate(w http.ResponseWriter, r *http.Request) *
|
|||
}
|
||||
|
||||
func (handler *Handler) createEndpoint(tx dataservices.DataStoreTx, payload *endpointCreatePayload) (*portainer.Endpoint, *httperror.HandlerError) {
|
||||
var err error
|
||||
|
||||
switch payload.EndpointCreationType {
|
||||
case azureEnvironment:
|
||||
return handler.createAzureEndpoint(tx, payload)
|
||||
|
@ -301,12 +298,9 @@ func (handler *Handler) createEndpoint(tx dataservices.DataStoreTx, payload *end
|
|||
endpointType := portainer.DockerEnvironment
|
||||
var agentVersion string
|
||||
if payload.EndpointCreationType == agentEnvironment {
|
||||
var tlsConfig *tls.Config
|
||||
if payload.TLS {
|
||||
tlsConfig, err = crypto.CreateTLSConfigurationFromBytes(payload.TLSCACertFile, payload.TLSCertFile, payload.TLSKeyFile, payload.TLSSkipVerify, payload.TLSSkipClientVerify)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to create TLS configuration", err)
|
||||
}
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromBytes(payload.TLS, payload.TLSCACertFile, payload.TLSCertFile, payload.TLSKeyFile, payload.TLSSkipVerify, payload.TLSSkipClientVerify)
|
||||
if err != nil {
|
||||
return nil, httperror.InternalServerError("Unable to create TLS configuration", err)
|
||||
}
|
||||
|
||||
agentPlatform, version, err := agent.GetAgentVersionAndPlatform(payload.URL, tlsConfig)
|
||||
|
|
|
@ -21,16 +21,14 @@ func initDial(endpoint *portainer.Endpoint) (net.Conn, error) {
|
|||
host = url.Path
|
||||
}
|
||||
|
||||
if endpoint.TLSConfig.TLS {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return tls.Dial(url.Scheme, host, tlsConfig)
|
||||
if !endpoint.TLSConfig.TLS {
|
||||
return createDial(url.Scheme, host)
|
||||
}
|
||||
|
||||
con, err := createDial(url.Scheme, host)
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return con, err
|
||||
return tls.Dial(url.Scheme, host, tlsConfig)
|
||||
}
|
||||
|
|
64
api/http/handler/websocket/initdial_test.go
Normal file
64
api/http/handler/websocket/initdial_test.go
Normal file
|
@ -0,0 +1,64 @@
|
|||
package websocket
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestInitDial(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
tlsSrv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer tlsSrv.Close()
|
||||
|
||||
f := func(srvURL string) {
|
||||
u, err := url.Parse(srvURL)
|
||||
require.NoError(t, err)
|
||||
|
||||
isTLS := u.Scheme == "https"
|
||||
|
||||
u.Scheme = "tcp"
|
||||
|
||||
endpoint := &portainer.Endpoint{
|
||||
URL: u.String(),
|
||||
TLSConfig: portainer.TLSConfiguration{
|
||||
TLS: isTLS,
|
||||
TLSSkipVerify: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Valid configuration
|
||||
conn, err := initDial(endpoint)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, conn)
|
||||
|
||||
err = conn.Close()
|
||||
require.NoError(t, err)
|
||||
|
||||
if !isTLS {
|
||||
return
|
||||
}
|
||||
|
||||
// Invalid TLS configuration
|
||||
endpoint.TLSConfig.TLSCertPath = "/invalid/path/client.crt"
|
||||
endpoint.TLSConfig.TLSKeyPath = "/invalid/path/client.key"
|
||||
|
||||
conn, err = initDial(endpoint)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, conn)
|
||||
}
|
||||
|
||||
f(srv.URL)
|
||||
f(tlsSrv.URL)
|
||||
}
|
|
@ -43,7 +43,7 @@ func (factory *ProxyFactory) NewAgentProxy(endpoint *portainer.Endpoint) (*Proxy
|
|||
httpTransport := &http.Transport{}
|
||||
|
||||
if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
|
||||
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed generating tls configuration")
|
||||
}
|
||||
|
|
|
@ -50,7 +50,7 @@ func (factory *ProxyFactory) newDockerHTTPProxy(endpoint *portainer.Endpoint) (h
|
|||
httpTransport := &http.Transport{}
|
||||
|
||||
if endpoint.TLSConfig.TLS || endpoint.TLSConfig.TLSSkipVerify {
|
||||
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||
config, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import (
|
|||
"github.com/portainer/portainer/api/http/proxy/factory/kubernetes"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
)
|
||||
|
||||
func (factory *ProxyFactory) newKubernetesProxy(endpoint *portainer.Endpoint) (http.Handler, error) {
|
||||
|
@ -93,19 +92,19 @@ func (factory *ProxyFactory) newKubernetesAgentHTTPSProxy(endpoint *portainer.En
|
|||
return nil, err
|
||||
}
|
||||
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig.TLSCACertPath, endpoint.TLSConfig.TLSCertPath, endpoint.TLSConfig.TLSKeyPath, endpoint.TLSConfig.TLSSkipVerify)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tokenCache := factory.kubernetesTokenCacheManager.GetOrCreateTokenCache(endpoint.ID)
|
||||
tokenManager, err := kubernetes.NewTokenManager(kubecli, factory.dataStore, tokenCache, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport, err := kubernetes.NewAgentTransport(factory.signatureService, tokenManager, endpoint, factory.kubernetesClientFactory, factory.dataStore, factory.jwtService)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := NewSingleHostReverseProxyWithHostHeader(remoteURL)
|
||||
proxy.Transport = kubernetes.NewAgentTransport(factory.signatureService, tlsConfig, tokenManager, endpoint, factory.kubernetesClientFactory, factory.dataStore, factory.jwtService)
|
||||
proxy.Transport = transport
|
||||
|
||||
return proxy, nil
|
||||
}
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
portainer "github.com/portainer/portainer/api"
|
||||
"github.com/portainer/portainer/api/crypto"
|
||||
"github.com/portainer/portainer/api/dataservices"
|
||||
"github.com/portainer/portainer/api/kubernetes/cli"
|
||||
)
|
||||
|
@ -16,7 +16,12 @@ type agentTransport struct {
|
|||
}
|
||||
|
||||
// NewAgentTransport returns a new transport that can be used to send signed requests to a Portainer agent
|
||||
func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsConfig *tls.Config, tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore, jwtService portainer.JWTService) *agentTransport {
|
||||
func NewAgentTransport(signatureService portainer.DigitalSignatureService, tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore, jwtService portainer.JWTService) (*agentTransport, error) {
|
||||
tlsConfig, err := crypto.CreateTLSConfigurationFromDisk(endpoint.TLSConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transport := &agentTransport{
|
||||
baseTransport: newBaseTransport(
|
||||
&http.Transport{
|
||||
|
@ -31,7 +36,7 @@ func NewAgentTransport(signatureService portainer.DigitalSignatureService, tlsCo
|
|||
signatureService: signatureService,
|
||||
}
|
||||
|
||||
return transport
|
||||
return transport, nil
|
||||
}
|
||||
|
||||
// RoundTrip is the implementation of the the http.RoundTripper interface
|
||||
|
|
|
@ -15,7 +15,7 @@ type localTransport struct {
|
|||
|
||||
// NewLocalTransport returns a new transport that can be used to send requests to the local Kubernetes API
|
||||
func NewLocalTransport(tokenManager *tokenManager, endpoint *portainer.Endpoint, k8sClientFactory *cli.ClientFactory, dataStore dataservices.DataStore, jwtService portainer.JWTService) (*localTransport, error) {
|
||||
config, err := crypto.CreateTLSConfigurationFromBytes(nil, nil, nil, true, true)
|
||||
config, err := crypto.CreateTLSConfigurationFromBytes(true, nil, nil, nil, true, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
13
api/http/proxy/factory/kubernetes/local_transport_test.go
Normal file
13
api/http/proxy/factory/kubernetes/local_transport_test.go
Normal file
|
@ -0,0 +1,13 @@
|
|||
package kubernetes
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNewLocalTransport(t *testing.T) {
|
||||
transport, err := NewLocalTransport(nil, nil, nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
require.True(t, transport.baseTransport.httpTransport.TLSClientConfig.InsecureSkipVerify)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue