mirror of https://github.com/portainer/portainer
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
185 lines
6.6 KiB
185 lines
6.6 KiB
2 years ago
|
package endpointutils
|
||
|
|
||
|
import (
|
||
|
"context"
|
||
|
"strings"
|
||
|
|
||
|
portainer "github.com/portainer/portainer/api"
|
||
|
"github.com/portainer/portainer/api/crypto"
|
||
|
"github.com/portainer/portainer/api/dataservices"
|
||
|
"github.com/portainer/portainer/api/http/client"
|
||
|
"github.com/rs/zerolog/log"
|
||
|
)
|
||
|
|
||
|
// InitEndpoint controls the workflow to initialize the primary endpoint.
|
||
|
// When installing Portainer in a Docker Cluster using the yaml file provided in the official
|
||
|
// documentation (https://docs.portainer.io/start/install/server/swarm/linux), the "primary"
|
||
|
// endpoint is initialized before the admin user is created. This triggers the creation of a
|
||
|
// snapshot of the environment in the background, which includes the agent saving the signature
|
||
|
// from the first request made by the server. However, if a user restores Portainer from a backup
|
||
|
// instead of creating a new admin user, the server will not be able to connect to the agent because
|
||
|
// the saved signature will not match. To solve this issue, this solution proposes to wait for
|
||
|
// the admin user to be created before initializing the primary endpoint. This way, the agent
|
||
|
// will save the signature from the first request after the admin user is created, ensuring that
|
||
|
// it matches in the event of a backup restoration.
|
||
|
func InitEndpoint(shutdownCtx context.Context, adminCreationDone <-chan struct{}, flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) {
|
||
|
|
||
|
select {
|
||
|
case <-shutdownCtx.Done():
|
||
|
log.Debug().Msg("shutdown endpoint initalization")
|
||
|
|
||
|
case <-adminCreationDone:
|
||
|
// Wait for the admin user to be created before initializing the primary endpoint
|
||
|
// The admin user can be created in two ways:
|
||
|
// 1. Using the CLI with the --admin-password flag
|
||
|
// 2. Using the API with the /api/users/admin/init endpoint
|
||
|
log.Debug().Msg("init primary endpoint")
|
||
|
|
||
|
err := initEndpoint(flags, dataStore, snapshotService)
|
||
|
if err != nil {
|
||
|
log.Fatal().Err(err).Msg("failed initializing environment")
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func initEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
||
|
if *flags.EndpointURL == "" {
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
endpoints, err := dataStore.Endpoint().Endpoints()
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
if len(endpoints) > 0 {
|
||
|
log.Info().Msg("instance already has defined environments, skipping the environment defined via CLI")
|
||
|
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
if *flags.TLS || *flags.TLSSkipVerify {
|
||
|
return createTLSSecuredEndpoint(flags, dataStore, snapshotService)
|
||
|
}
|
||
|
return createUnsecuredEndpoint(*flags.EndpointURL, dataStore, snapshotService)
|
||
|
}
|
||
|
|
||
|
func createTLSSecuredEndpoint(flags *portainer.CLIFlags, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) 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
|
||
|
}
|
||
|
|
||
|
endpointID := dataStore.Endpoint().GetNextIdentifier()
|
||
|
endpoint := &portainer.Endpoint{
|
||
|
ID: portainer.EndpointID(endpointID),
|
||
|
Name: "primary",
|
||
|
URL: *flags.EndpointURL,
|
||
|
GroupID: portainer.EndpointGroupID(1),
|
||
|
Type: portainer.DockerEnvironment,
|
||
|
TLSConfig: tlsConfiguration,
|
||
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||
|
TagIDs: []portainer.TagID{},
|
||
|
Status: portainer.EndpointStatusUp,
|
||
|
Snapshots: []portainer.DockerSnapshot{},
|
||
|
Kubernetes: portainer.KubernetesDefault(),
|
||
|
|
||
|
SecuritySettings: portainer.EndpointSecuritySettings{
|
||
|
AllowVolumeBrowserForRegularUsers: false,
|
||
|
EnableHostManagementFeatures: false,
|
||
|
|
||
|
AllowSysctlSettingForRegularUsers: true,
|
||
|
AllowBindMountsForRegularUsers: true,
|
||
|
AllowPrivilegedModeForRegularUsers: true,
|
||
|
AllowHostNamespaceForRegularUsers: true,
|
||
|
AllowContainerCapabilitiesForRegularUsers: true,
|
||
|
AllowDeviceMappingForRegularUsers: true,
|
||
|
AllowStackManagementForRegularUsers: true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
|
||
|
err := snapshotService.SnapshotEndpoint(endpoint)
|
||
|
if err != nil {
|
||
|
log.Error().
|
||
|
Str("endpoint", endpoint.Name).
|
||
|
Str("URL", endpoint.URL).
|
||
|
Err(err).
|
||
|
Msg("environment snapshot error")
|
||
|
}
|
||
|
|
||
|
return dataStore.Endpoint().Create(endpoint)
|
||
|
}
|
||
|
|
||
|
func createUnsecuredEndpoint(endpointURL string, dataStore dataservices.DataStore, snapshotService portainer.SnapshotService) error {
|
||
|
if strings.HasPrefix(endpointURL, "tcp://") {
|
||
|
_, err := client.ExecutePingOperation(endpointURL, nil)
|
||
|
if err != nil {
|
||
|
return err
|
||
|
}
|
||
|
}
|
||
|
|
||
|
endpointID := dataStore.Endpoint().GetNextIdentifier()
|
||
|
endpoint := &portainer.Endpoint{
|
||
|
ID: portainer.EndpointID(endpointID),
|
||
|
Name: "primary",
|
||
|
URL: endpointURL,
|
||
|
GroupID: portainer.EndpointGroupID(1),
|
||
|
Type: portainer.DockerEnvironment,
|
||
|
TLSConfig: portainer.TLSConfiguration{},
|
||
|
UserAccessPolicies: portainer.UserAccessPolicies{},
|
||
|
TeamAccessPolicies: portainer.TeamAccessPolicies{},
|
||
|
TagIDs: []portainer.TagID{},
|
||
|
Status: portainer.EndpointStatusUp,
|
||
|
Snapshots: []portainer.DockerSnapshot{},
|
||
|
Kubernetes: portainer.KubernetesDefault(),
|
||
|
|
||
|
SecuritySettings: portainer.EndpointSecuritySettings{
|
||
|
AllowVolumeBrowserForRegularUsers: false,
|
||
|
EnableHostManagementFeatures: false,
|
||
|
|
||
|
AllowSysctlSettingForRegularUsers: true,
|
||
|
AllowBindMountsForRegularUsers: true,
|
||
|
AllowPrivilegedModeForRegularUsers: true,
|
||
|
AllowHostNamespaceForRegularUsers: true,
|
||
|
AllowContainerCapabilitiesForRegularUsers: true,
|
||
|
AllowDeviceMappingForRegularUsers: true,
|
||
|
AllowStackManagementForRegularUsers: true,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
err := snapshotService.SnapshotEndpoint(endpoint)
|
||
|
if err != nil {
|
||
|
log.Error().
|
||
|
Str("endpoint", endpoint.Name).
|
||
|
Str("URL", endpoint.URL).Err(err).
|
||
|
Msg("environment snapshot error")
|
||
|
}
|
||
|
|
||
|
return dataStore.Endpoint().Create(endpoint)
|
||
|
}
|