Embed webassets natively into teleport instead of attaching to the binary (#5935)

Switch to go1.16. Use embed package to embed webassets instead of ad-hoc attaching to binary
This commit is contained in:
a-palchikov
2021-03-26 17:37:52 -07:00
committed by GitHub
parent b1bb78a9e0
commit d301da5a39
13 changed files with 269 additions and 214 deletions
+18 -18
View File
@@ -10,7 +10,7 @@ type: kubernetes
name: test
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -333,7 +333,7 @@ type: kubernetes
name: push-build-linux-amd64
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -427,7 +427,7 @@ type: kubernetes
name: push-build-linux-386
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -521,7 +521,7 @@ type: kubernetes
name: push-build-linux-amd64-fips
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -619,7 +619,7 @@ type: kubernetes
name: push-build-windows-amd64
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -810,7 +810,7 @@ type: kubernetes
name: push-build-linux-arm
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -904,7 +904,7 @@ type: kubernetes
name: push-build-linux-arm64
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -998,7 +998,7 @@ type: kubernetes
name: push-build-linux-arm64-fips
environment:
GID: "1000"
RUNTIME: go1.15.5
RUNTIME: go1.16.2
UID: "1000"
trigger:
event:
@@ -1326,7 +1326,7 @@ kind: pipeline
type: kubernetes
name: build-linux-amd64
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -1422,7 +1422,7 @@ kind: pipeline
type: kubernetes
name: build-linux-amd64-fips
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -1518,7 +1518,7 @@ kind: pipeline
type: kubernetes
name: build-linux-amd64-centos6
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -1617,7 +1617,7 @@ kind: pipeline
type: kubernetes
name: build-linux-amd64-centos6-fips
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -2181,7 +2181,7 @@ kind: pipeline
type: kubernetes
name: build-linux-386
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -2899,7 +2899,7 @@ kind: pipeline
type: kubernetes
name: build-linux-arm
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -2995,7 +2995,7 @@ kind: pipeline
type: kubernetes
name: build-linux-arm64
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -3091,7 +3091,7 @@ kind: pipeline
type: kubernetes
name: build-linux-arm64-fips
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -3889,7 +3889,7 @@ kind: pipeline
type: kubernetes
name: build-windows-amd64
environment:
RUNTIME: go1.15.5
RUNTIME: go1.16.2
trigger:
event:
include:
@@ -4742,6 +4742,6 @@ volumes:
name: drone-s3-debrepo-pvc
---
kind: signature
hmac: cffe37fc9bdcc947e927875a202940b47559076b0f67333295813e7bdb8ff422
hmac: 1ca2684a9adf84751a36a3c891b01e82011050bc1b6d284ef92f33acddbef71d
...
+3
View File
@@ -5,6 +5,9 @@ debug
default.etcd
darwin
# web asset directory
/lib/web/build
# usually release tarballs get in the way
*.gz
*.zip
+13 -9
View File
@@ -18,6 +18,7 @@ DOCKER_IMAGE_CI ?= quay.io/gravitational/teleport-ci
# These are standard autotools variables, don't change them please
BUILDDIR ?= build
ASSETS_BUILDDIR ?= lib/web/build
BINDIR ?= /usr/local/bin
DATADIR ?= /usr/local/share/teleport
ADDFLAGS ?=
@@ -120,7 +121,7 @@ $(BUILDDIR)/tctl:
.PHONY: $(BUILDDIR)/teleport
$(BUILDDIR)/teleport: ensure-webassets
GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "$(PAM_TAG) $(FIPS_TAG) $(BPF_TAG)" -o $(BUILDDIR)/teleport $(BUILDFLAGS) ./tool/teleport
GOOS=$(OS) GOARCH=$(ARCH) $(CGOFLAG) go build -tags "$(PAM_TAG) $(FIPS_TAG) $(BPF_TAG) $(WEBASSETS_TAG)" -o $(BUILDDIR)/teleport $(BUILDFLAGS) ./tool/teleport
.PHONY: $(BUILDDIR)/tsh
$(BUILDDIR)/tsh:
@@ -132,12 +133,9 @@ $(BUILDDIR)/tsh:
# only tsh is built.
#
.PHONY:full
full: all $(BUILDDIR)/webassets.zip
full: $(ASSETS_BUILDDIR)/webassets.zip
ifneq ("$(OS)", "windows")
@echo "---> Attaching OSS web assets."
cat $(BUILDDIR)/webassets.zip >> $(BUILDDIR)/teleport
rm -fr $(BUILDDIR)/webassets.zip
zip -q -A $(BUILDDIR)/teleport
$(MAKE) all WEBASSETS_TAG="webassets_embed"
endif
#
@@ -211,7 +209,10 @@ release-unix: clean full
tar -czf $(RELEASE).tar.gz teleport
rm -rf teleport
@echo "---> Created $(RELEASE).tar.gz."
@if [ -f e/Makefile ]; then $(MAKE) -C e release; fi
@if [ -f e/Makefile ]; then \
rm -fr $(WEBASSETS_BUILDDIR)/webassets.zip; \
$(MAKE) -C e release; \
fi
#
# make release-windows - Produces a binary release tarball containing teleport,
@@ -372,12 +373,15 @@ tag:
# build/webassets.zip archive contains the web assets (UI) which gets
# appended to teleport binary
$(BUILDDIR)/webassets.zip:
$(ASSETS_BUILDDIR)/webassets.zip: | $(ASSETS_BUILDDIR)
ifneq ("$(OS)", "windows")
@echo "---> Building OSS web assets."
cd webassets/teleport/ ; zip -qr ../../$(BUILDDIR)/webassets.zip .
cd webassets/teleport/ ; zip -qr ../../$@ .
endif
$(ASSETS_BUILDDIR):
mkdir -p $@
.PHONY: test-package
test-package: remove-temp-files
go test -v ./$(p)
+1 -1
View File
@@ -97,7 +97,7 @@ ENV GOGOPROTO_ROOT ${GOPATH}/src/github.com/gogo/protobuf
RUN (curl -L -o /tmp/${PROTOC_TARBALL} https://github.com/google/protobuf/releases/download/v${PROTOC_VER}/${PROTOC_TARBALL} && \
cd /tmp && unzip /tmp/${PROTOC_TARBALL} -d /usr/local && \
rm /tmp/${PROTOC_TARBALL})
RUN (go get -u github.com/gogo/protobuf/proto github.com/gogo/protobuf/protoc-gen-gogo github.com/gogo/protobuf/gogoproto golang.org/x/tools/cmd/goimports && \
RUN (git clone https://github.com/gogo/protobuf.git ${GOPATH}/src/github.com/gogo/protobuf && go install golang.org/x/tools/cmd/goimports@latest && \
cd ${GOPATH}/src/github.com/gogo/protobuf && \
git reset --hard ${GOGO_PROTO_TAG} && \
make install)
+1 -1
View File
@@ -13,7 +13,7 @@ TEST_KUBE ?=
OS ?= linux
ARCH ?= amd64
RUNTIME ?= go1.15.5
RUNTIME ?= go1.16.2
UID := $$(id -u)
GID := $$(id -g)
+1 -1
View File
@@ -55,7 +55,7 @@ var (
// TODO(gus): Set this from `make -C build.assets print-runtime-version` or similar rather
// than hardcoding it. Also remove the usage of RUNTIME as a pipeline-level environment variable
// (as support for these varies among Drone runners) and only set it for steps that need it.
goRuntime = value{raw: "go1.15.5"}
goRuntime = value{raw: "go1.16.2"}
)
type buildType struct {
+1 -1
Submodule e updated: 248c7900d7...03805e26e5
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/gravitational/teleport
go 1.15
go 1.16
require (
cloud.google.com/go v0.60.0
+2 -2
View File
@@ -3409,8 +3409,8 @@ func findPublicAddr(authClient auth.AccessPoint, a App) (string, error) {
// It uses external configuration to make the decision
func newHTTPFileSystem() (http.FileSystem, error) {
if !isDebugMode() {
fs, err := web.NewStaticFileSystem()
if err != nil {
fs, err := web.NewStaticFileSystem() //nolint:staticcheck
if err != nil { //nolint:staticcheck
return nil, trace.Wrap(err)
}
return fs, nil
+169
View File
@@ -0,0 +1,169 @@
/*
Copyright 2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package web
import (
"archive/zip"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/gravitational/trace"
"github.com/kardianos/osext"
)
// NewDebugFileSystem returns the HTTP file system implementation rooted
// at the specified assetsPath.
func NewDebugFileSystem(assetsPath string) (http.FileSystem, error) {
assetsToCheck := []string{"index.html", "/app"}
if assetsPath == "" {
exePath, err := osext.ExecutableFolder()
if err != nil {
return nil, trace.Wrap(err)
}
_, err = os.Stat(path.Join(exePath, "../../e"))
isEnterprise := !os.IsNotExist(err)
if isEnterprise {
// enterprise web assets
assetsPath = path.Join(exePath, "../../webassets/e/teleport")
} else {
// community web assets
assetsPath = path.Join(exePath, "../webassets/teleport")
}
}
for _, af := range assetsToCheck {
_, err := os.Stat(filepath.Join(assetsPath, af))
if err != nil {
return nil, trace.Wrap(err)
}
}
log.Infof("Using filesystem for serving web assets: %s.", assetsPath)
return http.Dir(assetsPath), nil
}
const (
webAssetsMissingError = "the teleport binary was built without web assets, try building with `make release`"
webAssetsReadError = "failure reading web assets from the binary"
)
func readZipArchive(r io.ReaderAt, size int64) (ResourceMap, error) {
zreader, err := zip.NewReader(r, size)
if err != nil {
// this often happens when teleport is launched without the web assets
// zip file attached to the binary. for launching it in such mode
// set DEBUG environment variable to 1
if err == zip.ErrFormat {
return nil, trace.NotFound(webAssetsMissingError)
}
return nil, trace.NotFound("%s %v", webAssetsReadError, err)
}
entries := make(ResourceMap)
for _, file := range zreader.File {
if file.FileInfo().IsDir() {
continue
}
entries[file.Name] = file
}
// no entries found?
if len(entries) == 0 {
return nil, trace.Wrap(os.ErrInvalid)
}
return entries, nil
}
// resource struct implements http.File interface on top of zip.File object
type resource struct {
reader io.ReadCloser
file *zip.File
pos int64
}
func (rsc *resource) Read(p []byte) (n int, err error) {
n, err = rsc.reader.Read(p)
rsc.pos += int64(n)
return n, err
}
func (rsc *resource) Seek(offset int64, whence int) (int64, error) {
var (
pos int64
err error
)
// zip.File does not support seeking. To implement Seek on top of it,
// we close the existing reader, re-open it, and read 'offset' bytes from
// the beginning
if err = rsc.reader.Close(); err != nil {
return 0, err
}
if rsc.reader, err = rsc.file.Open(); err != nil {
return 0, err
}
switch whence {
case io.SeekStart:
pos = offset
case io.SeekCurrent:
pos = rsc.pos + offset
case io.SeekEnd:
pos = int64(rsc.file.UncompressedSize64) + offset
}
if pos > 0 {
b := make([]byte, pos)
if _, err = rsc.reader.Read(b); err != nil {
return 0, err
}
}
rsc.pos = pos
return pos, nil
}
func (rsc *resource) Readdir(count int) ([]os.FileInfo, error) {
return nil, trace.Wrap(os.ErrPermission)
}
func (rsc *resource) Stat() (os.FileInfo, error) {
return rsc.file.FileInfo(), nil
}
func (rsc *resource) Close() (err error) {
log.Debugf("zip::Close(%s).", rsc.file.FileInfo().Name())
return rsc.reader.Close()
}
type ResourceMap map[string]*zip.File
func (rm ResourceMap) Open(name string) (http.File, error) {
log.Debugf("GET zip:%s.", name)
f, ok := rm[strings.Trim(name, "/")]
if !ok {
return nil, trace.Wrap(os.ErrNotExist)
}
reader, err := f.Open()
if err != nil {
return nil, trace.Wrap(err)
}
return &resource{
reader: reader,
file: f,
}, nil
}
+6 -179
View File
@@ -1,5 +1,7 @@
// +build !webassets_embed
/*
Copyright 2015 Gravitational, Inc.
Copyright 2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -12,192 +14,17 @@ distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package web
import (
"archive/zip"
"io"
"net/http"
"os"
"path"
"path/filepath"
"strings"
"github.com/gravitational/trace"
"github.com/kardianos/osext"
)
const (
webAssetsMissingError = "the teleport binary was built without web assets, try building with `make release`"
webAssetsReadError = "failure reading web assets from the binary"
)
// NewStaticFileSystem returns the initialized implementation of http.FileSystem
// interface which can be used to serve Teleport Proxy Web UI
func NewStaticFileSystem() (http.FileSystem, error) {
// Use the zip archive attached to the executable:
return loadZippedExeAssets()
}
// NewDebugFileSystem returns the HTTP file system implementation rooted
// at the specified assetsPath.
func NewDebugFileSystem(assetsPath string) (http.FileSystem, error) {
assetsToCheck := []string{"index.html", "/app"}
if assetsPath == "" {
exePath, err := osext.ExecutableFolder()
if err != nil {
return nil, trace.Wrap(err)
}
_, err = os.Stat(path.Join(exePath, "../../e"))
isEnterprise := !os.IsNotExist(err)
if isEnterprise {
// enterprise web assets
assetsPath = path.Join(exePath, "../../webassets/e/teleport")
} else {
// community web assets
assetsPath = path.Join(exePath, "../webassets/teleport")
}
}
for _, af := range assetsToCheck {
_, err := os.Stat(filepath.Join(assetsPath, af))
if err != nil {
return nil, trace.Wrap(err)
}
}
log.Infof("Using filesystem for serving web assets: %s.", assetsPath)
return http.Dir(assetsPath), nil
}
// LoadWebResources returns a filesystem implementation compatible
// with http.Serve.
//
// The "filesystem" is served from a zip file attached at the end of
// the executable
//
func loadZippedExeAssets() (ResourceMap, error) {
// open ourselves (teleport binary) for reading:
// NOTE: the file stays open to serve future Read() requests
myExe, err := osext.Executable()
if err != nil {
return nil, trace.Wrap(err)
}
return readZipArchive(myExe)
}
func readZipArchive(archivePath string) (ResourceMap, error) {
file, err := os.Open(archivePath)
if err != nil {
return nil, trace.Wrap(err)
}
// feed the binary into the zip reader and enumerate all files
// found in the attached zip file:
info, err := file.Stat()
if err != nil {
return nil, trace.Wrap(err)
}
zreader, err := zip.NewReader(file, info.Size())
if err != nil {
// this often happens when teleport is launched without the web assets
// zip file attached to the binary. for launching it in such mode
// set DEBUG environment variable to 1
if err == zip.ErrFormat {
return nil, trace.NotFound(webAssetsMissingError)
}
return nil, trace.NotFound("%s %v", webAssetsReadError, err)
}
entries := make(ResourceMap)
for _, file := range zreader.File {
if file.FileInfo().IsDir() {
continue
}
entries[file.Name] = file
}
// no entries found?
if len(entries) == 0 {
return nil, trace.Wrap(os.ErrInvalid)
}
return entries, nil
}
// resource struct implements http.File interface on top of zip.File object
type resource struct {
reader io.ReadCloser
file *zip.File
pos int64
}
func (rsc *resource) Read(p []byte) (n int, err error) {
n, err = rsc.reader.Read(p)
rsc.pos += int64(n)
return n, err
}
func (rsc *resource) Seek(offset int64, whence int) (int64, error) {
var (
pos int64
err error
)
// zip.File does not support seeking. To implement Seek on top of it,
// we close the existing reader, re-open it, and read 'offset' bytes from
// the beginning
if err = rsc.reader.Close(); err != nil {
return 0, err
}
if rsc.reader, err = rsc.file.Open(); err != nil {
return 0, err
}
switch whence {
case io.SeekStart:
pos = offset
case io.SeekCurrent:
pos = rsc.pos + offset
case io.SeekEnd:
pos = int64(rsc.file.UncompressedSize64) + offset
}
if pos > 0 {
b := make([]byte, pos)
if _, err = rsc.reader.Read(b); err != nil {
return 0, err
}
}
rsc.pos = pos
return pos, nil
}
func (rsc *resource) Readdir(count int) ([]os.FileInfo, error) {
return nil, trace.Wrap(os.ErrPermission)
}
func (rsc *resource) Stat() (os.FileInfo, error) {
return rsc.file.FileInfo(), nil
}
func (rsc *resource) Close() (err error) {
log.Debugf("zip::Close(%s).", rsc.file.FileInfo().Name())
return rsc.reader.Close()
}
type ResourceMap map[string]*zip.File
func (rm ResourceMap) Open(name string) (http.File, error) {
log.Debugf("GET zip:%s.", name)
f, ok := rm[strings.Trim(name, "/")]
if !ok {
return nil, trace.Wrap(os.ErrNotExist)
}
reader, err := f.Open()
if err != nil {
return nil, trace.Wrap(err)
}
return &resource{
reader: reader,
file: f,
}, nil
// NewStaticFileSystem is a no-op in this build mode.
func NewStaticFileSystem() (http.FileSystem, error) { //nolint:staticcheck
return nil, trace.NotFound(webAssetsMissingError)
}
+34
View File
@@ -0,0 +1,34 @@
// +build webassets_embed
/*
Copyright 2021 Gravitational, Inc.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package web
import (
"bytes"
_ "embed"
"net/http"
)
//go:embed build/webassets.zip
var webassetsZip []byte
// NewStaticFileSystem returns the initialized implementation of http.FileSystem
// interface which can be used to serve Teleport Proxy Web UI
func NewStaticFileSystem() (http.FileSystem, error) {
return readZipArchive(bytes.NewReader(webassetsZip), int64(len(webassetsZip)))
}
+19 -1
View File
@@ -19,8 +19,10 @@ package web
import (
"io"
"io/ioutil"
"os"
"strings"
"github.com/gravitational/trace"
"gopkg.in/check.v1"
)
@@ -46,7 +48,7 @@ func (s *StaticSuite) TestLocalFS(c *check.C) {
}
func (s *StaticSuite) TestZipFS(c *check.C) {
fs, err := readZipArchive("../../fixtures/assets.zip")
fs, err := readZipArchiveAt("../../fixtures/assets.zip")
c.Assert(err, check.IsNil)
c.Assert(fs, check.NotNil)
@@ -87,3 +89,19 @@ func (s *StaticSuite) TestZipFS(c *check.C) {
c.Assert(err, check.IsNil)
c.Assert(len(bytes), check.Equals, 100)
}
func readZipArchiveAt(path string) (ResourceMap, error) {
file, err := os.Open(path)
if err != nil {
return nil, trace.Wrap(err)
}
// file needs to stay open for http.FileSystem reads to work
//
// feed the binary into the zip reader and enumerate all files
// found in the attached zip file:
info, err := file.Stat()
if err != nil {
return nil, trace.Wrap(err)
}
return readZipArchive(file, info.Size())
}