-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
1,149 additions
and
1 deletion.
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 |
---|---|---|
|
@@ -3,4 +3,5 @@ go 1.21 | |
use ( | ||
. | ||
./service-mesh | ||
./node-installer | ||
) |
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,44 @@ | ||
# Contrast node installer | ||
|
||
This program runs as a daemonset on every CC-enabled node of a Kubernetes cluster. | ||
It expects the host filesystem of the node to be mounted under `/host`. | ||
On start, it will read a configuration file under `$CONFIG_DIR/contrast-node-install.json` and install binary artifacts on the host filesystem according to the configuration. | ||
After installing binary artifacts, it installs and patches configuration files for the Contrast runtime class `contrast-cc-isolation` and restarts containerd. | ||
|
||
## Configuration | ||
|
||
By default, the installer ships with a config file under `/config/contrast-node-install.json`, which takes binary artifacts from the container image. | ||
If desired, you can replace the configuration using a Kubernetes configmap by mounting it into the container. | ||
|
||
- `files`: List of files to be installed. | ||
- `files[*].url`: Source of the file's content. Use `http://` or `https://` to download it or `file://` to copy a file from the container image. | ||
- `files[*].path`: Target location of the file on the host filesystem. | ||
- `files[*].integrity`: Expected Subresource Integrity (SRI) digest of the file. Only required if url starts with `http://` or `https://`. | ||
|
||
Consider the following example: | ||
|
||
```json | ||
{ | ||
"files": [ | ||
{ | ||
"url": "https://cdn.confidential.cloud/contrast/node-components/2024-03-13/kata-containers.img", | ||
"path": "/opt/edgeless/share/kata-containers.img", | ||
"integrity": "sha256-EdFywKAU+xD0BXmmfbjV4cB6Gqbq9R9AnMWoZFCM3A0=" | ||
}, | ||
{ | ||
"url": "https://cdn.confidential.cloud/contrast/node-components/2024-03-13/kata-containers-igvm.img", | ||
"path": "/opt/edgeless/share/kata-containers-igvm.img", | ||
"integrity": "sha256-E9Ttx6f9QYwKlQonO/fl1bF2MNBoU4XG3/HHvt9Zv30=" | ||
}, | ||
{ | ||
"url": "https://cdn.confidential.cloud/contrast/node-components/2024-03-13/cloud-hypervisor-cvm", | ||
"path": "/opt/edgeless/bin/cloud-hypervisor-snp", | ||
"integrity": "sha256-coTHzd5/QLjlPQfrp9d2TJTIXKNuANTN7aNmpa8PRXo=" | ||
}, | ||
{ | ||
"url": "file:///opt/edgeless/bin/containerd-shim-contrast-cc-v2", | ||
"path": "/opt/edgeless/bin/containerd-shim-contrast-cc-v2", | ||
} | ||
] | ||
} | ||
``` |
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,8 @@ | ||
module github.com/edgelesssys/contrast/node-installer | ||
|
||
go 1.21 | ||
|
||
require ( | ||
github.com/pelletier/go-toml v1.9.5 | ||
golang.org/x/sys v0.18.0 | ||
) |
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,4 @@ | ||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= | ||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= | ||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= | ||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
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,102 @@ | ||
package asset | ||
|
||
import ( | ||
"context" | ||
"crypto/sha256" | ||
"crypto/sha512" | ||
"encoding/base64" | ||
"fmt" | ||
"hash" | ||
"net/url" | ||
"sync" | ||
) | ||
|
||
// Fetcher can retrieve assets from various sources. | ||
// It works by delegating to a handler for the scheme of the source URI. | ||
type Fetcher struct { | ||
handlers map[string]handler | ||
mux sync.RWMutex | ||
} | ||
|
||
// NewDefaultFetcher creates a new fetcher with default handlers. | ||
func NewDefaultFetcher() *Fetcher { | ||
fetcher := NewFetcher() | ||
fetcher.RegisterHandler("file", NewFileFetcher()) | ||
fetcher.RegisterHandler("http", NewHTTPFetcher()) | ||
fetcher.RegisterHandler("https", NewHTTPFetcher()) | ||
return fetcher | ||
} | ||
|
||
// NewFetcher creates a new fetcher. | ||
func NewFetcher() *Fetcher { | ||
return &Fetcher{ | ||
handlers: make(map[string]handler), | ||
} | ||
} | ||
|
||
// Fetch retrieves a file from a source URI. | ||
func (f *Fetcher) Fetch(ctx context.Context, sourceURI, destination, integrity string) (changed bool, retErr error) { | ||
uri, err := url.Parse(sourceURI) | ||
if err != nil { | ||
return false, err | ||
} | ||
hasher, expectedSum, err := hashFromIntegrity(integrity) | ||
if err != nil { | ||
return false, err | ||
} | ||
schemeFetcher := f.getHandler(uri.Scheme) | ||
if schemeFetcher == nil { | ||
return false, fmt.Errorf("no handler for scheme %s", uri.Scheme) | ||
} | ||
return schemeFetcher.Fetch(ctx, uri, destination, expectedSum, hasher) | ||
} | ||
|
||
// FetchUnchecked retrieves a file from a source URI without verifying its integrity. | ||
func (f *Fetcher) FetchUnchecked(ctx context.Context, sourceURI, destination string) error { | ||
uri, err := url.Parse(sourceURI) | ||
if err != nil { | ||
return err | ||
} | ||
schemeFetcher := f.getHandler(uri.Scheme) | ||
if schemeFetcher == nil { | ||
return fmt.Errorf("no handler for scheme %s", uri.Scheme) | ||
} | ||
return schemeFetcher.FetchUnchecked(ctx, uri, destination) | ||
} | ||
|
||
// RegisterHandler registers a handler for a scheme. | ||
func (f *Fetcher) RegisterHandler(scheme string, h handler) { | ||
f.mux.Lock() | ||
defer f.mux.Unlock() | ||
f.handlers[scheme] = h | ||
} | ||
|
||
func (f *Fetcher) getHandler(scheme string) handler { | ||
f.mux.RLock() | ||
defer f.mux.RUnlock() | ||
return f.handlers[scheme] | ||
} | ||
|
||
type handler interface { | ||
Fetch(ctx context.Context, uri *url.URL, destination string, expectedSum []byte, hasher hash.Hash) (bool, error) | ||
FetchUnchecked(ctx context.Context, uri *url.URL, destination string) error | ||
} | ||
|
||
func hashFromIntegrity(integrity string) (hash.Hash, []byte, error) { | ||
var hash hash.Hash | ||
switch integrity[:7] { | ||
case "sha256-": | ||
hash = sha256.New() | ||
case "sha384-": | ||
hash = sha512.New384() | ||
case "sha512-": | ||
hash = sha512.New() | ||
default: | ||
return nil, nil, fmt.Errorf("unsupported hash algorithm: %s", integrity[:7]) | ||
} | ||
expectedSum, err := base64.StdEncoding.DecodeString(integrity[7:]) | ||
if err != nil { | ||
return nil, nil, fmt.Errorf("decoding integrity value: %w", err) | ||
} | ||
return hash, expectedSum, 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,65 @@ | ||
package asset | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
"hash" | ||
"io" | ||
"net/url" | ||
"os" | ||
"slices" | ||
|
||
"github.com/edgelesssys/contrast/node-installer/internal/fileop" | ||
) | ||
|
||
// FileFetcher is a Fetcher that retrieves assets from a file. | ||
// It handles the "file" scheme. | ||
type FileFetcher struct { | ||
copier copier | ||
} | ||
|
||
// NewFileFetcher creates a new file fetcher. | ||
func NewFileFetcher() *FileFetcher { | ||
return &FileFetcher{copier: fileop.NewDefault()} | ||
} | ||
|
||
// Fetch retrieves a file from the local filesystem. | ||
func (f *FileFetcher) Fetch(_ context.Context, uri *url.URL, destination string, expectedSum []byte, hasher hash.Hash) (bool, error) { | ||
if uri.Scheme != "file" { | ||
return false, fmt.Errorf("file fetcher does not support scheme %s", uri.Scheme) | ||
} | ||
sourceFile, err := os.Open(uri.Path) | ||
if err != nil { | ||
return false, fmt.Errorf("opening file: %w", err) | ||
} | ||
defer sourceFile.Close() | ||
|
||
if _, err := io.Copy(hasher, sourceFile); err != nil { | ||
return false, fmt.Errorf("hashing file: %w", err) | ||
} | ||
if err := sourceFile.Close(); err != nil { | ||
return false, fmt.Errorf("closing file: %w", err) | ||
} | ||
actualSum := hasher.Sum(nil) | ||
if !slices.Equal(actualSum, expectedSum) { | ||
return false, fmt.Errorf("file hash mismatch: expected %x, got %x", expectedSum, actualSum) | ||
} | ||
changed, err := f.copier.CopyOnDiff(uri.Path, destination) | ||
if err != nil { | ||
return false, fmt.Errorf("copying file: %w", err) | ||
} | ||
return changed, nil | ||
} | ||
|
||
// FetchUnchecked retrieves a file from the local filesystem without verifying its integrity. | ||
func (f *FileFetcher) FetchUnchecked(_ context.Context, uri *url.URL, destination string) error { | ||
if uri.Scheme != "file" { | ||
return fmt.Errorf("file fetcher does not support scheme %s", uri.Scheme) | ||
} | ||
_, err := f.copier.CopyOnDiff(uri.Path, destination) | ||
return err | ||
} | ||
|
||
type copier interface { | ||
CopyOnDiff(src, dst string) (bool, error) | ||
} |
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,114 @@ | ||
package asset | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"fmt" | ||
"hash" | ||
"io" | ||
"net/http" | ||
"net/url" | ||
"os" | ||
|
||
"github.com/edgelesssys/contrast/node-installer/internal/fileop" | ||
) | ||
|
||
// HTTPFetcher is a Fetcher that retrieves assets from http(s). | ||
// It handles the "http" and "https" schemes. | ||
type HTTPFetcher struct { | ||
mover mover | ||
client *http.Client | ||
} | ||
|
||
// NewHTTPFetcher creates a new HTTP fetcher. | ||
func NewHTTPFetcher() *HTTPFetcher { | ||
return &HTTPFetcher{mover: fileop.NewDefault(), client: http.DefaultClient} | ||
} | ||
|
||
// Fetch retrieves a file from an HTTP server. | ||
func (f *HTTPFetcher) Fetch(ctx context.Context, uri *url.URL, destination string, expectedSum []byte, hasher hash.Hash) (bool, error) { | ||
if uri.Scheme != "http" && uri.Scheme != "https" { | ||
return false, fmt.Errorf("http fetcher does not support scheme %s", uri.Scheme) | ||
} | ||
|
||
if existing, err := os.Open(destination); err == nil { | ||
defer existing.Close() | ||
if _, err := io.Copy(hasher, existing); err != nil { | ||
return false, fmt.Errorf("hashing existing file %s: %w", destination, err) | ||
} | ||
if sum := hasher.Sum(nil); bytes.Equal(sum, expectedSum) { | ||
// File already exists and has the correct hash | ||
return false, nil | ||
} | ||
hasher.Reset() | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) | ||
if err != nil { | ||
return false, fmt.Errorf("creating request: %w", err) | ||
} | ||
response, err := f.client.Do(req) | ||
if err != nil { | ||
return false, fmt.Errorf("fetching file: %w", err) | ||
} | ||
defer response.Body.Close() | ||
|
||
tmpfile, err := os.CreateTemp("", "download") | ||
if err != nil { | ||
return false, err | ||
} | ||
defer tmpfile.Close() | ||
defer os.Remove(tmpfile.Name()) | ||
|
||
reader := io.TeeReader(response.Body, hasher) | ||
|
||
if _, err := io.Copy(tmpfile, reader); err != nil { | ||
return false, fmt.Errorf("downloading file contents from %s: %w", uri.String(), err) | ||
} | ||
|
||
sum := hasher.Sum(nil) | ||
if !bytes.Equal(sum, expectedSum) { | ||
return false, fmt.Errorf("hash mismatch for %s: expected %x, got %x", uri.String(), expectedSum, sum) | ||
} | ||
if err := tmpfile.Sync(); err != nil { | ||
return false, fmt.Errorf("syncing file %s: %w", tmpfile.Name(), err) | ||
} | ||
if err := f.mover.Move(tmpfile.Name(), destination); err != nil { | ||
return false, fmt.Errorf("moving file: %w", err) | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
// FetchUnchecked retrieves a file from an HTTP server without verifying its integrity. | ||
func (f *HTTPFetcher) FetchUnchecked(ctx context.Context, uri *url.URL, destination string) error { | ||
if uri.Scheme != "http" && uri.Scheme != "https" { | ||
return fmt.Errorf("http fetcher does not support scheme %s", uri.Scheme) | ||
} | ||
|
||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, uri.String(), nil) | ||
if err != nil { | ||
return fmt.Errorf("creating request: %w", err) | ||
} | ||
response, err := f.client.Do(req) | ||
if err != nil { | ||
return fmt.Errorf("fetching file: %w", err) | ||
} | ||
defer response.Body.Close() | ||
|
||
dstFile, err := os.Create(destination) | ||
if err != nil { | ||
return err | ||
} | ||
defer dstFile.Close() | ||
|
||
_, err = io.Copy(dstFile, response.Body) | ||
if err != nil { | ||
return fmt.Errorf("downloading file contents from %s: %w", uri.String(), err) | ||
} | ||
return nil | ||
} | ||
|
||
type mover interface { | ||
Move(src, dst string) error | ||
} |
Oops, something went wrong.