Skip to content

Commit

Permalink
Export endpoint config
Browse files Browse the repository at this point in the history
  • Loading branch information
bernardjkim committed Jun 11, 2024
1 parent 80abcca commit db34e75
Show file tree
Hide file tree
Showing 5 changed files with 233 additions and 4 deletions.
15 changes: 15 additions & 0 deletions lib/service/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ import (
"github.com/gravitational/teleport/lib/utils/cert"
logutils "github.com/gravitational/teleport/lib/utils/log"
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"
webapp "github.com/gravitational/teleport/lib/web/app"
Expand Down Expand Up @@ -1105,6 +1106,20 @@ func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) {
process.logger.WarnContext(process.ExitContext(), "Use of external upgraders on control-plane instances is not recommended.")
}

if upgraderKind == "unit" {
process.RegisterFunc("autoupdates.endpoint.export", func() error {
if err := endpoint.Export(process.ExitContext(), resolverAddr); err != nil {
process.logger.WarnContext(process.ExitContext(),
"Failed to export and validate autoupdates endpoint.",
"addr", resolverAddr.String(),
"error", err)
return trace.Wrap(err)
}
process.logger.InfoContext(process.ExitContext(), "Exported autoupdates endpoint.", "addr", 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"
)
101 changes: 101 additions & 0 deletions lib/versioncontrol/endpoint/endpoint.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* 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"

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

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

// Export exports the proxy version server config.
func Export(ctx context.Context, proxyAddr utils.NetAddr) error {
versionEndpoint := fmt.Sprint(path.Join(proxyAddr.String(), 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 string(b), 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 @@ -36,6 +36,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 @@ -44,9 +45,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 @@ -399,7 +397,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 db34e75

Please sign in to comment.