Skip to content

Commit

Permalink
[v14] Export endpoint config (#44377)
Browse files Browse the repository at this point in the history
* Export endpoint config

* Export endpoint only for cloud instances

* Trim newline

* Fix import order

* process.logger unavailable in v14

* Remove extra arg
  • Loading branch information
bernardjkim authored Jul 22, 2024
1 parent d2ca431 commit dc7d892
Show file tree
Hide file tree
Showing 5 changed files with 251 additions and 4 deletions.
32 changes: 32 additions & 0 deletions lib/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ import (
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/cert"
vc "github.com/gravitational/teleport/lib/versioncontrol"
"github.com/gravitational/teleport/lib/versioncontrol/endpoint"
uw "github.com/gravitational/teleport/lib/versioncontrol/upgradewindow"
"github.com/gravitational/teleport/lib/web"
)
Expand Down Expand Up @@ -1076,6 +1077,37 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) {
process.log.Warnf("Use of external upgraders on control-plane instances is not recommended.")
}

if upgraderKind == "unit" {
process.RegisterFunc("autoupdates.endpoint.export", func() error {
component := teleport.Component("autoupdates:endpoint:export", process.id)
logger := process.log.WithFields(logrus.Fields{
trace.Component: component,
})
conn, err := waitForInstanceConnector(process, logger)
if err != nil {
return trace.Wrap(err)
}
if conn == nil {
return trace.BadParameter("process exiting and Instance connector never became available")
}

resp, err := conn.Client.Ping(process.ExitContext())
if err != nil {
return trace.Wrap(err)
}
if !resp.GetServerFeatures().GetCloud() {
return nil
}

if err := endpoint.Export(process.ExitContext(), resolverAddr.String()); err != nil {
logger.Warnf("Failed to export and validate autoupdates endpoint (addr=%s): %v", resolverAddr.String(), err)
return trace.Wrap(err)
}
logger.Infof("Exported autoupdates endpoint (addr=%s).", resolverAddr.String())
return nil
})
}

driver, err := uw.NewDriver(upgraderKind)
if err != nil {
return nil, trace.Wrap(err)
Expand Down
24 changes: 24 additions & 0 deletions lib/versioncontrol/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package versioncontrol

const (
// UnitConfigDir is the configuration directory of the teleport-upgrade unit.
UnitConfigDir = "/etc/teleport-upgrade.d"
)
102 changes: 102 additions & 0 deletions lib/versioncontrol/endpoint/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package endpoint

import (
"context"
"fmt"
"io"
"net/url"
"os"
"path"
"strings"

"github.com/gravitational/trace"

versionlib "github.com/gravitational/teleport/lib/automaticupgrades/version"
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/versioncontrol"
)

const stableCloudPath = "v1/webapi/automaticupgrades/channel/stable/cloud"

// Export exports the proxy version server config.
func Export(ctx context.Context, proxyAddr string) error {
versionEndpoint := fmt.Sprint(path.Join(proxyAddr, stableCloudPath))
if err := verifyVersionEndpoint(ctx, versionEndpoint); err != nil {
return trace.Wrap(err, "version endpoint may be invalid or unreachable")
}

appliedEndpoint, err := exportEndpoint(versioncontrol.UnitConfigDir, versionEndpoint)
if err != nil {
return trace.Wrap(err, "failed to export version endpoint")
}

if err := verifyVersionEndpoint(ctx, appliedEndpoint); err != nil {
return trace.Wrap(err, "applied version endpoint may be invalid or unreachable")
}

return nil
}

// verifyVersionEndpoint verifies that the provided endpoint serves a valid teleport
// version.
func verifyVersionEndpoint(ctx context.Context, endpoint string) error {
baseURL, err := url.Parse(fmt.Sprintf("https://%s", endpoint))
if err != nil {
return trace.Wrap(err)
}
versionGetter := versionlib.NewBasicHTTPVersionGetter(baseURL)
_, err = versionGetter.GetVersion(ctx)
return trace.Wrap(err)
}

// exportEndpoint exports the versionEndpoint to the specified config directory.
// If an existing value is already present, it will not be overwritten. The resulting
// version endpoint value will be returned.
func exportEndpoint(configDir, versionEndpoint string) (string, error) {
// ensure config dir exists. if created it is set to 755, which is reasonably safe and seems to
// be the standard choice for config dirs like this in /etc/.
if err := os.MkdirAll(configDir, defaults.DirectoryPermissions); err != nil {
return "", trace.Wrap(err)
}

// open/create endpoint file. if created it is set to 644, which is reasonable for a sensitive but non-secret config value.
endpointFile, err := os.OpenFile(path.Join(configDir, "endpoint"), os.O_RDWR|os.O_CREATE, defaults.FilePermissions)
if err != nil {
return "", trace.Wrap(err, "failed to open endpoint config file")
}
defer endpointFile.Close()

b, err := io.ReadAll(endpointFile)
if err != nil {
return "", trace.Wrap(err, "failed to read endpoint config file")
}

// Do not overwrite if an endpoint value is already configured.
if len(b) != 0 {
return strings.TrimSuffix(string(b), "\n"), nil
}

_, err = endpointFile.Write([]byte(versionEndpoint))
if err != nil {
return "", trace.Wrap(err, "failed to write endpoint config file")
}
return versionEndpoint, nil
}
91 changes: 91 additions & 0 deletions lib/versioncontrol/endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

package endpoint

import (
"os"
"path"
"testing"

"github.com/stretchr/testify/require"
)

const testDir = "export-endpoint-test"

func Test_exportEndpoint(t *testing.T) {
tests := []struct {
name string
endpoint string
expected string
initConfigDir func() string
}{
{
name: "create endpoint file and write value",
endpoint: "v1/stable/cloud",
expected: "v1/stable/cloud",
initConfigDir: func() string {
tmpDir, err := os.MkdirTemp("", testDir)
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(tmpDir) })
return tmpDir
},
},
{
name: "write value",
endpoint: "v1/stable/cloud",
expected: "v1/stable/cloud",
initConfigDir: func() string {
tmpDir, err := os.MkdirTemp("", testDir)
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(tmpDir) })

endpointFile, err := os.Create(path.Join(tmpDir, "endpoint"))
require.NoError(t, err)
require.NoError(t, endpointFile.Close())
return tmpDir
},
},
{
name: "endpoint value already configured",
endpoint: "v1/stable/cloud",
expected: "existing/endpoint",
initConfigDir: func() string {
tmpDir, err := os.MkdirTemp("", testDir)
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(tmpDir) })

endpointFile, err := os.Create(path.Join(tmpDir, "endpoint"))
require.NoError(t, err)

_, err = endpointFile.Write([]byte("existing/endpoint"))
require.NoError(t, err)
require.NoError(t, endpointFile.Close())
return tmpDir
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
configDir := tt.initConfigDir()
appliedEndpoint, err := exportEndpoint(configDir, tt.endpoint)
require.NoError(t, err)
require.Equal(t, tt.expected, appliedEndpoint)
})
}
}
6 changes: 2 additions & 4 deletions lib/versioncontrol/upgradewindow/upgradewindow.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/gravitational/teleport/lib/defaults"
"github.com/gravitational/teleport/lib/utils"
"github.com/gravitational/teleport/lib/utils/interval"
"github.com/gravitational/teleport/lib/versioncontrol"
)

const (
Expand All @@ -42,9 +43,6 @@ const (

// unitScheduleFile is the name of the file to which the unit schedule is exported.
unitScheduleFile = "schedule"

// unitConfigDir is the configuration directory of the teleport-upgrade unit.
unitConfigDir = "/etc/teleport-upgrade.d"
)

// ExportFunc represents the ExportUpgradeWindows rpc exposed by auth servers.
Expand Down Expand Up @@ -397,7 +395,7 @@ type systemdDriver struct {

func NewSystemdUnitDriver(cfg SystemdUnitDriverConfig) (Driver, error) {
if cfg.ConfigDir == "" {
cfg.ConfigDir = unitConfigDir
cfg.ConfigDir = versioncontrol.UnitConfigDir
}

return &systemdDriver{cfg: cfg}, nil
Expand Down

0 comments on commit dc7d892

Please sign in to comment.