-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add client tools auto update * Replace fork for posix platform for re-exec Move integration tests to client tools specific dir Use context cancellation with SIGTERM, SIGINT Remove cancelable tee reader with context replacement Renaming * Fix syscall path execution Fix archive cleanup if hash is not valid Limit the archive write bytes * Cover the case with single package for darwin platform after v17 * Move updater logic to tools package * Move context out from the library Base URL renaming * Add more context in comments * Changes in find endpoint * Replace test http server with `httptest` Replace hash for bytes matching Proper temp file close for archive download * Add more context to comments * Move feature flag to main package to be reused * Constant rename * Replace build tag with lib/modules to identify enterprise build * Replace fips tag with modules flag
- Loading branch information
Showing
11 changed files
with
1,283 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
/* | ||
* 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 tools_test | ||
|
||
import ( | ||
"net/http" | ||
"sync" | ||
) | ||
|
||
type limitRequest struct { | ||
limit int64 | ||
lock chan struct{} | ||
} | ||
|
||
// limitedResponseWriter wraps http.ResponseWriter and enforces a write limit | ||
// then block the response until signal is received. | ||
type limitedResponseWriter struct { | ||
requests chan limitRequest | ||
} | ||
|
||
// newLimitedResponseWriter creates a new limitedResponseWriter with the lock. | ||
func newLimitedResponseWriter() *limitedResponseWriter { | ||
lw := &limitedResponseWriter{ | ||
requests: make(chan limitRequest, 10), | ||
} | ||
return lw | ||
} | ||
|
||
// Wrap wraps response writer if limit was previously requested, if not, return original one. | ||
func (lw *limitedResponseWriter) Wrap(w http.ResponseWriter) http.ResponseWriter { | ||
select { | ||
case request := <-lw.requests: | ||
return &wrapper{ | ||
ResponseWriter: w, | ||
request: request, | ||
} | ||
default: | ||
return w | ||
} | ||
} | ||
|
||
// SetLimitRequest sends limit request to the pool to wrap next response writer with defined limits. | ||
func (lw *limitedResponseWriter) SetLimitRequest(limit limitRequest) { | ||
lw.requests <- limit | ||
} | ||
|
||
// wrapper wraps the http response writer to control writing operation by blocking it. | ||
type wrapper struct { | ||
http.ResponseWriter | ||
|
||
written int64 | ||
request limitRequest | ||
released bool | ||
|
||
mutex sync.Mutex | ||
} | ||
|
||
// Write writes data to the underlying ResponseWriter but respects the byte limit. | ||
func (lw *wrapper) Write(p []byte) (int, error) { | ||
lw.mutex.Lock() | ||
defer lw.mutex.Unlock() | ||
|
||
if lw.written >= lw.request.limit && !lw.released { | ||
// Send signal that lock is acquired and wait till it was released by response. | ||
lw.request.lock <- struct{}{} | ||
<-lw.request.lock | ||
lw.released = true | ||
} | ||
|
||
n, err := lw.ResponseWriter.Write(p) | ||
lw.written += int64(n) | ||
return n, err | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
//go:build !windows | ||
|
||
/* | ||
* 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 tools_test | ||
|
||
import ( | ||
"errors" | ||
"syscall" | ||
|
||
"github.com/gravitational/trace" | ||
) | ||
|
||
// sendInterrupt sends a SIGINT to the process. | ||
func sendInterrupt(pid int) error { | ||
err := syscall.Kill(pid, syscall.SIGINT) | ||
if errors.Is(err, syscall.ESRCH) { | ||
return trace.BadParameter("can't find the process: %v", pid) | ||
} | ||
return trace.Wrap(err) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
//go:build windows | ||
|
||
/* | ||
* 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 tools_test | ||
|
||
import ( | ||
"syscall" | ||
|
||
"github.com/gravitational/trace" | ||
"golang.org/x/sys/windows" | ||
) | ||
|
||
var ( | ||
kernel = windows.NewLazyDLL("kernel32.dll") | ||
ctrlEvent = kernel.NewProc("GenerateConsoleCtrlEvent") | ||
) | ||
|
||
// sendInterrupt sends a Ctrl-Break event to the process. | ||
func sendInterrupt(pid int) error { | ||
r, _, err := ctrlEvent.Call(uintptr(syscall.CTRL_BREAK_EVENT), uintptr(pid)) | ||
if r == 0 { | ||
return trace.Wrap(err) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,173 @@ | ||
/* | ||
* 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 tools_test | ||
|
||
import ( | ||
"context" | ||
"crypto/sha256" | ||
"encoding/hex" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"log" | ||
"net/http" | ||
"net/http/httptest" | ||
"os" | ||
"os/exec" | ||
"path/filepath" | ||
"runtime" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/gravitational/trace" | ||
|
||
"github.com/gravitational/teleport/integration/helpers" | ||
) | ||
|
||
const ( | ||
testBinaryName = "updater" | ||
teleportToolsVersion = "TELEPORT_TOOLS_VERSION" | ||
) | ||
|
||
var ( | ||
// testVersions list of the pre-compiled binaries with encoded versions to check. | ||
testVersions = []string{ | ||
"1.2.3", | ||
"3.2.1", | ||
} | ||
limitedWriter = newLimitedResponseWriter() | ||
|
||
toolsDir string | ||
baseURL string | ||
) | ||
|
||
func TestMain(m *testing.M) { | ||
ctx := context.Background() | ||
tmp, err := os.MkdirTemp(os.TempDir(), testBinaryName) | ||
if err != nil { | ||
log.Fatalf("failed to create temporary directory: %v", err) | ||
} | ||
|
||
toolsDir, err = os.MkdirTemp(os.TempDir(), "tools") | ||
if err != nil { | ||
log.Fatalf("failed to create temporary directory: %v", err) | ||
} | ||
|
||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
filePath := filepath.Join(tmp, r.URL.Path) | ||
switch { | ||
case strings.HasSuffix(r.URL.Path, ".sha256"): | ||
serve256File(w, r, strings.TrimSuffix(filePath, ".sha256")) | ||
default: | ||
http.ServeFile(limitedWriter.Wrap(w), r, filePath) | ||
} | ||
})) | ||
baseURL = server.URL | ||
for _, version := range testVersions { | ||
if err := buildAndArchiveApps(ctx, tmp, toolsDir, version, server.URL); err != nil { | ||
log.Fatalf("failed to build testing app binary archive: %v", err) | ||
} | ||
} | ||
|
||
// Run tests after binary is built. | ||
code := m.Run() | ||
|
||
server.Close() | ||
if err := os.RemoveAll(tmp); err != nil { | ||
log.Fatalf("failed to remove temporary directory: %v", err) | ||
} | ||
if err := os.RemoveAll(toolsDir); err != nil { | ||
log.Fatalf("failed to remove tools directory: %v", err) | ||
} | ||
|
||
os.Exit(code) | ||
} | ||
|
||
// serve256File calculates sha256 checksum for requested file. | ||
func serve256File(w http.ResponseWriter, _ *http.Request, filePath string) { | ||
log.Printf("Calculating and serving file checksum: %s\n", filePath) | ||
|
||
w.Header().Set("Content-Disposition", "attachment; filename=\""+filepath.Base(filePath)+".sha256\"") | ||
w.Header().Set("Content-Type", "plain/text") | ||
|
||
file, err := os.Open(filePath) | ||
if errors.Is(err, os.ErrNotExist) { | ||
http.Error(w, "file not found", http.StatusNotFound) | ||
return | ||
} | ||
if err != nil { | ||
http.Error(w, "failed to open file", http.StatusInternalServerError) | ||
return | ||
} | ||
defer file.Close() | ||
|
||
hash := sha256.New() | ||
if _, err := io.Copy(hash, file); err != nil { | ||
http.Error(w, "failed to write to hash", http.StatusInternalServerError) | ||
return | ||
} | ||
if _, err := hex.NewEncoder(w).Write(hash.Sum(nil)); err != nil { | ||
http.Error(w, "failed to write checksum", http.StatusInternalServerError) | ||
} | ||
} | ||
|
||
// buildAndArchiveApps compiles the updater integration and pack it depends on platform is used. | ||
func buildAndArchiveApps(ctx context.Context, path string, toolsDir string, version string, baseURL string) error { | ||
versionPath := filepath.Join(path, version) | ||
for _, app := range []string{"tsh", "tctl"} { | ||
output := filepath.Join(versionPath, app) | ||
switch runtime.GOOS { | ||
case "windows": | ||
output = filepath.Join(versionPath, app+".exe") | ||
case "darwin": | ||
output = filepath.Join(versionPath, app+".app", "Contents", "MacOS", app) | ||
} | ||
if err := buildBinary(output, toolsDir, version, baseURL); err != nil { | ||
return trace.Wrap(err) | ||
} | ||
} | ||
switch runtime.GOOS { | ||
case "darwin": | ||
archivePath := filepath.Join(path, fmt.Sprintf("teleport-%s.pkg", version)) | ||
return trace.Wrap(helpers.CompressDirToPkgFile(ctx, versionPath, archivePath, "com.example.pkgtest")) | ||
case "windows": | ||
archivePath := filepath.Join(path, fmt.Sprintf("teleport-v%s-windows-amd64-bin.zip", version)) | ||
return trace.Wrap(helpers.CompressDirToZipFile(ctx, versionPath, archivePath)) | ||
default: | ||
archivePath := filepath.Join(path, fmt.Sprintf("teleport-v%s-linux-%s-bin.tar.gz", version, runtime.GOARCH)) | ||
return trace.Wrap(helpers.CompressDirToTarGzFile(ctx, versionPath, archivePath)) | ||
} | ||
} | ||
|
||
// buildBinary executes command to build binary with updater logic only for testing. | ||
func buildBinary(output string, toolsDir string, version string, baseURL string) error { | ||
cmd := exec.Command( | ||
"go", "build", "-o", output, | ||
"-ldflags", strings.Join([]string{ | ||
fmt.Sprintf("-X 'main.toolsDir=%s'", toolsDir), | ||
fmt.Sprintf("-X 'main.version=%s'", version), | ||
fmt.Sprintf("-X 'main.baseURL=%s'", baseURL), | ||
}, " "), | ||
"./updater", | ||
) | ||
cmd.Stdout = os.Stdout | ||
cmd.Stderr = os.Stderr | ||
|
||
return trace.Wrap(cmd.Run()) | ||
} |
Oops, something went wrong.