package kubernetes import ( "crypto/tls" "fmt" "net/http" "github.com/portainer/portainer/api/http/security" portainer "github.com/portainer/portainer/api" "github.com/portainer/portainer/api/crypto" ) type ( localTransport struct { httpTransport *http.Transport tokenManager *tokenManager } agentTransport struct { httpTransport *http.Transport tokenManager *tokenManager signatureService portainer.DigitalSignatureService } edgeTransport struct { httpTransport *http.Transport tokenManager *tokenManager reverseTunnelService portainer.ReverseTunnelService endpointIdentifier portainer.EndpointID } ) // NewLocalTransport returns a new transport that can be used to send requests to the local Kubernetes API func NewLocalTransport(tokenManager *tokenManager) (*localTransport, error) { config, err := crypto.CreateTLSConfigurationFromBytes(nil, nil, nil, true, true) if err != nil { return nil, err } transport := &localTransport{ httpTransport: &http.Transport{ TLSClientConfig: config, }, tokenManager: tokenManager, } return transport, nil } // RoundTrip is the implementation of the the http.RoundTripper interface func (transport *localTransport) RoundTrip(request *http.Request) (*http.Response, error) { tokenData, err := security.RetrieveTokenData(request) if err != nil { return nil, err } var token string if tokenData.Role == portainer.AdministratorRole { token = transport.tokenManager.getAdminServiceAccountToken() } else { token, err = transport.tokenManager.getUserServiceAccountToken(int(tokenData.ID)) if err != nil { return nil, err } } request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token)) return transport.httpTransport.RoundTrip(request) } // 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) *agentTransport { transport := &agentTransport{ httpTransport: &http.Transport{ TLSClientConfig: tlsConfig, }, tokenManager: tokenManager, signatureService: signatureService, } return transport } // RoundTrip is the implementation of the the http.RoundTripper interface func (transport *agentTransport) RoundTrip(request *http.Request) (*http.Response, error) { tokenData, err := security.RetrieveTokenData(request) if err != nil { return nil, err } var token string if tokenData.Role == portainer.AdministratorRole { token = transport.tokenManager.getAdminServiceAccountToken() } else { token, err = transport.tokenManager.getUserServiceAccountToken(int(tokenData.ID)) if err != nil { return nil, err } } request.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token) signature, err := transport.signatureService.CreateSignature(portainer.PortainerAgentSignatureMessage) if err != nil { return nil, err } request.Header.Set(portainer.PortainerAgentPublicKeyHeader, transport.signatureService.EncodedPublicKey()) request.Header.Set(portainer.PortainerAgentSignatureHeader, signature) return transport.httpTransport.RoundTrip(request) } // NewAgentTransport returns a new transport that can be used to send signed requests to a Portainer Edge agent func NewEdgeTransport(reverseTunnelService portainer.ReverseTunnelService, endpointIdentifier portainer.EndpointID, tokenManager *tokenManager) *edgeTransport { transport := &edgeTransport{ httpTransport: &http.Transport{}, tokenManager: tokenManager, reverseTunnelService: reverseTunnelService, endpointIdentifier: endpointIdentifier, } return transport } // RoundTrip is the implementation of the the http.RoundTripper interface func (transport *edgeTransport) RoundTrip(request *http.Request) (*http.Response, error) { tokenData, err := security.RetrieveTokenData(request) if err != nil { return nil, err } var token string if tokenData.Role == portainer.AdministratorRole { token = transport.tokenManager.getAdminServiceAccountToken() } else { token, err = transport.tokenManager.getUserServiceAccountToken(int(tokenData.ID)) if err != nil { return nil, err } } request.Header.Set(portainer.PortainerAgentKubernetesSATokenHeader, token) response, err := transport.httpTransport.RoundTrip(request) if err == nil { transport.reverseTunnelService.SetTunnelStatusToActive(transport.endpointIdentifier) } else { transport.reverseTunnelService.SetTunnelStatusToIdle(transport.endpointIdentifier) } return response, err }