2018-06-11 13:13:19 +00:00
package endpoints
import (
2019-07-25 22:38:07 +00:00
"errors"
"net"
2018-06-11 13:13:19 +00:00
"net/http"
2019-07-25 22:38:07 +00:00
"net/url"
2018-07-20 09:02:06 +00:00
"runtime"
2018-06-11 13:13:19 +00:00
"strconv"
2019-09-20 04:14:19 +00:00
"strings"
2018-06-11 13:13:19 +00:00
2018-09-10 10:01:38 +00:00
httperror "github.com/portainer/libhttp/error"
"github.com/portainer/libhttp/request"
"github.com/portainer/libhttp/response"
2019-03-21 01:20:14 +00:00
"github.com/portainer/portainer/api"
"github.com/portainer/portainer/api/crypto"
"github.com/portainer/portainer/api/http/client"
2018-06-11 13:13:19 +00:00
)
type endpointCreatePayload struct {
2020-05-19 03:08:57 +00:00
Name string
URL string
EndpointType int
PublicURL string
GroupID int
TLS bool
TLSSkipVerify bool
TLSSkipClientVerify bool
TLSCACertFile [ ] byte
TLSCertFile [ ] byte
TLSKeyFile [ ] byte
TagIDs [ ] portainer . TagID
2020-06-04 05:35:09 +00:00
EdgeCheckinInterval int
2018-06-11 13:13:19 +00:00
}
func ( payload * endpointCreatePayload ) Validate ( r * http . Request ) error {
name , err := request . RetrieveMultiPartFormValue ( r , "Name" , false )
if err != nil {
2018-09-02 08:35:05 +00:00
return portainer . Error ( "Invalid endpoint name" )
2018-06-11 13:13:19 +00:00
}
payload . Name = name
endpointType , err := request . RetrieveNumericMultiPartFormValue ( r , "EndpointType" , false )
if err != nil || endpointType == 0 {
2020-05-19 03:08:57 +00:00
return portainer . Error ( "Invalid endpoint type value. Value must be one of: 1 (Docker environment), 2 (Agent environment) or 4 (Edge Agent environment)" )
2018-06-11 13:13:19 +00:00
}
payload . EndpointType = endpointType
groupID , _ := request . RetrieveNumericMultiPartFormValue ( r , "GroupID" , true )
if groupID == 0 {
groupID = 1
}
payload . GroupID = groupID
2020-03-29 09:54:14 +00:00
var tagIDs [ ] portainer . TagID
err = request . RetrieveMultiPartFormJSONValue ( r , "TagIds" , & tagIDs , true )
2018-06-15 07:18:25 +00:00
if err != nil {
2020-03-29 09:54:14 +00:00
return portainer . Error ( "Invalid TagIds parameter" )
2018-06-15 07:18:25 +00:00
}
2020-03-29 09:54:14 +00:00
payload . TagIDs = tagIDs
if payload . TagIDs == nil {
payload . TagIDs = make ( [ ] portainer . TagID , 0 )
2018-07-26 08:13:18 +00:00
}
2018-06-15 07:18:25 +00:00
2018-06-11 13:13:19 +00:00
useTLS , _ := request . RetrieveBooleanMultiPartFormValue ( r , "TLS" , true )
payload . TLS = useTLS
if payload . TLS {
skipTLSServerVerification , _ := request . RetrieveBooleanMultiPartFormValue ( r , "TLSSkipVerify" , true )
payload . TLSSkipVerify = skipTLSServerVerification
skipTLSClientVerification , _ := request . RetrieveBooleanMultiPartFormValue ( r , "TLSSkipClientVerify" , true )
payload . TLSSkipClientVerify = skipTLSClientVerification
if ! payload . TLSSkipVerify {
2018-09-10 10:01:38 +00:00
caCert , _ , err := request . RetrieveMultiPartFormFile ( r , "TLSCACertFile" )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid CA certificate file. Ensure that the file is uploaded correctly" )
}
payload . TLSCACertFile = caCert
}
if ! payload . TLSSkipClientVerify {
2018-09-10 10:01:38 +00:00
cert , _ , err := request . RetrieveMultiPartFormFile ( r , "TLSCertFile" )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid certificate file. Ensure that the file is uploaded correctly" )
}
payload . TLSCertFile = cert
2018-09-10 10:01:38 +00:00
key , _ , err := request . RetrieveMultiPartFormFile ( r , "TLSKeyFile" )
2018-06-11 13:13:19 +00:00
if err != nil {
return portainer . Error ( "Invalid key file. Ensure that the file is uploaded correctly" )
}
payload . TLSKeyFile = key
}
}
2020-05-19 03:08:57 +00:00
endpointURL , err := request . RetrieveMultiPartFormValue ( r , "URL" , true )
if err != nil {
return portainer . Error ( "Invalid endpoint URL" )
2018-06-11 13:13:19 +00:00
}
2020-05-19 03:08:57 +00:00
payload . URL = endpointURL
publicURL , _ := request . RetrieveMultiPartFormValue ( r , "PublicURL" , true )
payload . PublicURL = publicURL
2018-06-11 13:13:19 +00:00
2020-06-04 05:35:09 +00:00
checkinInterval , _ := request . RetrieveNumericMultiPartFormValue ( r , "CheckinInterval" , true )
payload . EdgeCheckinInterval = checkinInterval
2018-06-11 13:13:19 +00:00
return nil
}
// POST request on /api/endpoints
func ( handler * Handler ) endpointCreate ( w http . ResponseWriter , r * http . Request ) * httperror . HandlerError {
payload := & endpointCreatePayload { }
err := payload . Validate ( r )
if err != nil {
return & httperror . HandlerError { http . StatusBadRequest , "Invalid request payload" , err }
}
endpoint , endpointCreationError := handler . createEndpoint ( payload )
if endpointCreationError != nil {
return endpointCreationError
}
2020-05-20 05:23:15 +00:00
endpointGroup , err := handler . DataStore . EndpointGroup ( ) . EndpointGroup ( endpoint . GroupID )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to find an endpoint group inside the database" , err }
}
2020-05-20 05:23:15 +00:00
edgeGroups , err := handler . DataStore . EdgeGroup ( ) . EdgeGroups ( )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve edge groups from the database" , err }
}
2020-05-20 05:23:15 +00:00
edgeStacks , err := handler . DataStore . EdgeStack ( ) . EdgeStacks ( )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to retrieve edge stacks from the database" , err }
}
relationObject := & portainer . EndpointRelation {
EndpointID : endpoint . ID ,
EdgeStacks : map [ portainer . EdgeStackID ] bool { } ,
}
if endpoint . Type == portainer . EdgeAgentEnvironment {
relatedEdgeStacks := portainer . EndpointRelatedEdgeStacks ( endpoint , endpointGroup , edgeGroups , edgeStacks )
for _ , stackID := range relatedEdgeStacks {
relationObject . EdgeStacks [ stackID ] = true
}
}
2020-05-20 05:23:15 +00:00
err = handler . DataStore . EndpointRelation ( ) . CreateEndpointRelation ( relationObject )
2020-05-14 02:14:28 +00:00
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist the relation object inside the database" , err }
}
2018-06-11 13:13:19 +00:00
return response . JSON ( w , endpoint )
}
func ( handler * Handler ) createEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
2020-05-19 03:08:57 +00:00
if portainer . EndpointType ( payload . EndpointType ) == portainer . EdgeAgentEnvironment {
2019-07-25 22:38:07 +00:00
return handler . createEdgeAgentEndpoint ( payload )
2018-06-11 13:13:19 +00:00
}
if payload . TLS {
return handler . createTLSSecuredEndpoint ( payload )
}
return handler . createUnsecuredEndpoint ( payload )
}
2019-07-25 22:38:07 +00:00
func ( handler * Handler ) createEdgeAgentEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
endpointType := portainer . EdgeAgentEnvironment
2020-05-20 05:23:15 +00:00
endpointID := handler . DataStore . Endpoint ( ) . GetNextIdentifier ( )
2019-07-25 22:38:07 +00:00
portainerURL , err := url . Parse ( payload . URL )
if err != nil {
return nil , & httperror . HandlerError { http . StatusBadRequest , "Invalid endpoint URL" , err }
}
portainerHost , _ , err := net . SplitHostPort ( portainerURL . Host )
if err != nil {
portainerHost = portainerURL . Host
}
if portainerHost == "localhost" {
return nil , & httperror . HandlerError { http . StatusBadRequest , "Invalid endpoint URL" , errors . New ( "cannot use localhost as endpoint URL" ) }
}
edgeKey := handler . ReverseTunnelService . GenerateEdgeKey ( payload . URL , portainerHost , endpointID )
endpoint := & portainer . Endpoint {
ID : portainer . EndpointID ( endpointID ) ,
Name : payload . Name ,
URL : portainerHost ,
Type : endpointType ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
TLSConfig : portainer . TLSConfiguration {
TLS : false ,
} ,
2020-06-04 05:35:09 +00:00
AuthorizedUsers : [ ] portainer . UserID { } ,
AuthorizedTeams : [ ] portainer . TeamID { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
TagIDs : payload . TagIDs ,
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
EdgeKey : edgeKey ,
EdgeCheckinInterval : payload . EdgeCheckinInterval ,
2019-07-25 22:38:07 +00:00
}
2019-10-07 02:42:01 +00:00
err = handler . saveEndpointAndUpdateAuthorizations ( endpoint )
2018-06-11 13:13:19 +00:00
if err != nil {
2019-10-07 02:42:01 +00:00
return nil , & httperror . HandlerError { http . StatusInternalServerError , "An error occured while trying to create the endpoint" , err }
2018-06-11 13:13:19 +00:00
}
return endpoint , nil
}
func ( handler * Handler ) createUnsecuredEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
endpointType := portainer . DockerEnvironment
2018-07-20 09:02:06 +00:00
if payload . URL == "" {
payload . URL = "unix:///var/run/docker.sock"
if runtime . GOOS == "windows" {
payload . URL = "npipe:////./pipe/docker_engine"
}
} else {
2018-06-11 13:13:19 +00:00
agentOnDockerEnvironment , err := client . ExecutePingOperation ( payload . URL , nil )
if err != nil {
return nil , & httperror . HandlerError { http . StatusInternalServerError , "Unable to ping Docker environment" , err }
}
if agentOnDockerEnvironment {
endpointType = portainer . AgentOnDockerEnvironment
}
}
2020-05-20 05:23:15 +00:00
endpointID := handler . DataStore . Endpoint ( ) . GetNextIdentifier ( )
2018-06-11 13:13:19 +00:00
endpoint := & portainer . Endpoint {
2018-07-24 12:47:19 +00:00
ID : portainer . EndpointID ( endpointID ) ,
2018-06-11 13:13:19 +00:00
Name : payload . Name ,
URL : payload . URL ,
Type : endpointType ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
PublicURL : payload . PublicURL ,
TLSConfig : portainer . TLSConfiguration {
TLS : false ,
} ,
2019-05-24 06:04:58 +00:00
UserAccessPolicies : portainer . UserAccessPolicies { } ,
TeamAccessPolicies : portainer . TeamAccessPolicies { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
2020-03-29 09:54:14 +00:00
TagIDs : payload . TagIDs ,
2019-05-24 06:04:58 +00:00
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
2018-06-11 13:13:19 +00:00
}
2018-07-23 07:51:33 +00:00
err := handler . snapshotAndPersistEndpoint ( endpoint )
2018-06-11 13:13:19 +00:00
if err != nil {
2018-07-23 07:51:33 +00:00
return nil , err
2018-06-11 13:13:19 +00:00
}
return endpoint , nil
}
func ( handler * Handler ) createTLSSecuredEndpoint ( payload * endpointCreatePayload ) ( * portainer . Endpoint , * httperror . HandlerError ) {
tlsConfig , err := crypto . CreateTLSConfigurationFromBytes ( payload . TLSCACertFile , payload . TLSCertFile , payload . TLSKeyFile , payload . TLSSkipClientVerify , payload . TLSSkipVerify )
if err != nil {
return nil , & httperror . HandlerError { http . StatusInternalServerError , "Unable to create TLS configuration" , err }
}
agentOnDockerEnvironment , err := client . ExecutePingOperation ( payload . URL , tlsConfig )
if err != nil {
return nil , & httperror . HandlerError { http . StatusInternalServerError , "Unable to ping Docker environment" , err }
}
endpointType := portainer . DockerEnvironment
if agentOnDockerEnvironment {
endpointType = portainer . AgentOnDockerEnvironment
}
2020-05-20 05:23:15 +00:00
endpointID := handler . DataStore . Endpoint ( ) . GetNextIdentifier ( )
2018-06-11 13:13:19 +00:00
endpoint := & portainer . Endpoint {
2018-07-24 12:47:19 +00:00
ID : portainer . EndpointID ( endpointID ) ,
2018-06-11 13:13:19 +00:00
Name : payload . Name ,
URL : payload . URL ,
Type : endpointType ,
GroupID : portainer . EndpointGroupID ( payload . GroupID ) ,
PublicURL : payload . PublicURL ,
TLSConfig : portainer . TLSConfiguration {
TLS : payload . TLS ,
TLSSkipVerify : payload . TLSSkipVerify ,
} ,
2019-05-24 06:04:58 +00:00
UserAccessPolicies : portainer . UserAccessPolicies { } ,
TeamAccessPolicies : portainer . TeamAccessPolicies { } ,
Extensions : [ ] portainer . EndpointExtension { } ,
2020-03-29 09:54:14 +00:00
TagIDs : payload . TagIDs ,
2019-05-24 06:04:58 +00:00
Status : portainer . EndpointStatusUp ,
Snapshots : [ ] portainer . Snapshot { } ,
2018-06-11 13:13:19 +00:00
}
filesystemError := handler . storeTLSFiles ( endpoint , payload )
if err != nil {
return nil , filesystemError
}
2018-07-24 12:47:19 +00:00
endpointCreationError := handler . snapshotAndPersistEndpoint ( endpoint )
if endpointCreationError != nil {
return nil , endpointCreationError
2018-06-11 13:13:19 +00:00
}
return endpoint , nil
}
2018-07-23 07:51:33 +00:00
func ( handler * Handler ) snapshotAndPersistEndpoint ( endpoint * portainer . Endpoint ) * httperror . HandlerError {
snapshot , err := handler . Snapshotter . CreateSnapshot ( endpoint )
endpoint . Status = portainer . EndpointStatusUp
if err != nil {
2019-09-20 04:14:19 +00:00
if strings . Contains ( err . Error ( ) , "Invalid request signature" ) {
err = errors . New ( "agent already paired with another Portainer instance" )
}
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to initiate communications with endpoint" , err }
2018-07-23 07:51:33 +00:00
}
if snapshot != nil {
endpoint . Snapshots = [ ] portainer . Snapshot { * snapshot }
}
2019-10-07 02:42:01 +00:00
err = handler . saveEndpointAndUpdateAuthorizations ( endpoint )
2018-07-23 07:51:33 +00:00
if err != nil {
2019-10-07 02:42:01 +00:00
return & httperror . HandlerError { http . StatusInternalServerError , "An error occured while trying to create the endpoint" , err }
}
return nil
}
func ( handler * Handler ) saveEndpointAndUpdateAuthorizations ( endpoint * portainer . Endpoint ) error {
2020-05-20 05:23:15 +00:00
err := handler . DataStore . Endpoint ( ) . CreateEndpoint ( endpoint )
2019-10-07 02:42:01 +00:00
if err != nil {
return err
}
2020-05-20 05:23:15 +00:00
group , err := handler . DataStore . EndpointGroup ( ) . EndpointGroup ( endpoint . GroupID )
2019-10-07 02:42:01 +00:00
if err != nil {
return err
}
if len ( group . UserAccessPolicies ) > 0 || len ( group . TeamAccessPolicies ) > 0 {
return handler . AuthorizationService . UpdateUsersAuthorizations ( )
2018-07-23 07:51:33 +00:00
}
2020-05-14 02:14:28 +00:00
for _ , tagID := range endpoint . TagIDs {
2020-05-20 05:23:15 +00:00
tag , err := handler . DataStore . Tag ( ) . Tag ( tagID )
2020-05-14 02:14:28 +00:00
if err != nil {
return err
}
tag . Endpoints [ endpoint . ID ] = true
2020-05-20 05:23:15 +00:00
err = handler . DataStore . Tag ( ) . UpdateTag ( tagID , tag )
2020-05-14 02:14:28 +00:00
if err != nil {
return err
}
}
2018-07-23 07:51:33 +00:00
return nil
}
2018-06-11 13:13:19 +00:00
func ( handler * Handler ) storeTLSFiles ( endpoint * portainer . Endpoint , payload * endpointCreatePayload ) * httperror . HandlerError {
folder := strconv . Itoa ( int ( endpoint . ID ) )
if ! payload . TLSSkipVerify {
caCertPath , err := handler . FileService . StoreTLSFileFromBytes ( folder , portainer . TLSFileCA , payload . TLSCACertFile )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist TLS CA certificate file on disk" , err }
}
endpoint . TLSConfig . TLSCACertPath = caCertPath
}
if ! payload . TLSSkipClientVerify {
certPath , err := handler . FileService . StoreTLSFileFromBytes ( folder , portainer . TLSFileCert , payload . TLSCertFile )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist TLS certificate file on disk" , err }
}
endpoint . TLSConfig . TLSCertPath = certPath
keyPath , err := handler . FileService . StoreTLSFileFromBytes ( folder , portainer . TLSFileKey , payload . TLSKeyFile )
if err != nil {
return & httperror . HandlerError { http . StatusInternalServerError , "Unable to persist TLS key file on disk" , err }
}
endpoint . TLSConfig . TLSKeyPath = keyPath
}
return nil
}