-
Notifications
You must be signed in to change notification settings - Fork 9
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
8 changed files
with
182 additions
and
94 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
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
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
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
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,6 @@ | ||
# Contrast SDK | ||
|
||
**Caution:** This SDK is still under active development and not fit for external use yet. | ||
Please expect breaking changes with new minor versions. | ||
|
||
The SDK allows writing programs that interact with a Contrast deployment like the CLI does, without relying on the CLI. |
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,72 @@ | ||
// Copyright 2024 Edgeless Systems GmbH | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
|
||
package sdk | ||
|
||
import ( | ||
"fmt" | ||
"log/slog" | ||
"os" | ||
"path/filepath" | ||
|
||
"github.com/edgelesssys/contrast/internal/atls" | ||
"github.com/edgelesssys/contrast/internal/attestation/certcache" | ||
"github.com/edgelesssys/contrast/internal/attestation/snp" | ||
"github.com/edgelesssys/contrast/internal/attestation/tdx" | ||
"github.com/edgelesssys/contrast/internal/fsstore" | ||
"github.com/edgelesssys/contrast/internal/logger" | ||
"github.com/edgelesssys/contrast/internal/manifest" | ||
) | ||
|
||
const cacheDirEnv = "CONTRAST_CACHE_DIR" | ||
|
||
// ValidatorsFromManifest returns a list of validators corresponding to the reference values in the given manifest. | ||
// Originally an unexported function in the contrast CLI. | ||
// Can be made unexported again, if we decide to move all userapi calls from the CLI to the SDK. | ||
func ValidatorsFromManifest(m *manifest.Manifest, log *slog.Logger, hostData []byte) ([]atls.Validator, error) { | ||
kdsDir, err := cachedir("kds") | ||
if err != nil { | ||
return nil, fmt.Errorf("getting cache dir: %w", err) | ||
} | ||
log.Debug("Using KDS cache dir", "dir", kdsDir) | ||
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache")) | ||
kdsGetter := certcache.NewCachedHTTPSGetter(kdsCache, certcache.NeverGCTicker, log.WithGroup("kds-getter")) | ||
|
||
var validators []atls.Validator | ||
|
||
opts, err := m.SNPValidateOpts(kdsGetter) | ||
if err != nil { | ||
return nil, fmt.Errorf("getting SNP validate options: %w", err) | ||
} | ||
for _, opt := range opts { | ||
opt.ValidateOpts.HostData = hostData | ||
validators = append(validators, snp.NewValidator(opt.VerifyOpts, opt.ValidateOpts, | ||
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}), | ||
)) | ||
} | ||
|
||
tdxOpts, err := m.TDXValidateOpts() | ||
if err != nil { | ||
return nil, fmt.Errorf("generating TDX validation options: %w", err) | ||
} | ||
var mrConfigID [48]byte | ||
copy(mrConfigID[:], hostData) | ||
for _, opt := range tdxOpts { | ||
opt.TdQuoteBodyOptions.MrConfigID = mrConfigID[:] | ||
validators = append(validators, tdx.NewValidator(&tdx.StaticValidateOptsGenerator{Opts: opt}, logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "tdx"}))) | ||
} | ||
|
||
return validators, nil | ||
} | ||
|
||
func cachedir(subdir string) (string, error) { | ||
dir := os.Getenv(cacheDirEnv) | ||
if dir == "" { | ||
cachedir, err := os.UserCacheDir() | ||
if err != nil { | ||
return "", err | ||
} | ||
dir = filepath.Join(cachedir, "contrast") | ||
} | ||
return filepath.Join(dir, subdir), 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,4 @@ | ||
// Copyright 2024 Edgeless Systems GmbH | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
|
||
package sdk |
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,90 @@ | ||
// Copyright 2024 Edgeless Systems GmbH | ||
// SPDX-License-Identifier: AGPL-3.0-only | ||
|
||
package sdk | ||
|
||
import ( | ||
"bytes" | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"log/slog" | ||
"net" | ||
|
||
"github.com/edgelesssys/contrast/internal/atls" | ||
"github.com/edgelesssys/contrast/internal/grpc/dialer" | ||
"github.com/edgelesssys/contrast/internal/manifest" | ||
"github.com/edgelesssys/contrast/internal/userapi" | ||
) | ||
|
||
// Client is used to interact with a Contrast deployment. | ||
type Client struct { | ||
log *slog.Logger | ||
} | ||
|
||
// New returns a Client. | ||
func New(log *slog.Logger) Client { | ||
return Client{ | ||
log: log, | ||
} | ||
} | ||
|
||
// Verify checks if a given manifest is the latest manifest in the given history. | ||
func Verify(expected []byte, history [][]byte) error { | ||
currentManifest := history[len(history)-1] | ||
if !bytes.Equal(currentManifest, expected) { | ||
return fmt.Errorf("active manifest does not match expected manifest") | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// GetManifests calls GetManifests on the coordinator's userapi. | ||
func (c Client) GetManifests(ctx context.Context, manifestBytes []byte, endpoint string, policyHash []byte) (GetManifestsResponse, error) { | ||
var m manifest.Manifest | ||
if err := json.Unmarshal(manifestBytes, &m); err != nil { | ||
return GetManifestsResponse{}, fmt.Errorf("unmarshalling manifest: %w", err) | ||
} | ||
if err := m.Validate(); err != nil { | ||
return GetManifestsResponse{}, fmt.Errorf("validating manifest: %w", err) | ||
} | ||
|
||
validators, err := ValidatorsFromManifest(&m, c.log, policyHash) | ||
if err != nil { | ||
return GetManifestsResponse{}, fmt.Errorf("getting validators: %w", err) | ||
} | ||
dialer := dialer.New(atls.NoIssuer, validators, atls.NoMetrics, &net.Dialer{}) | ||
|
||
c.log.Debug("Dialing coordinator", "endpoint", endpoint) | ||
|
||
conn, err := dialer.Dial(ctx, endpoint) | ||
if err != nil { | ||
return GetManifestsResponse{}, fmt.Errorf("dialing coordinator: %w", err) | ||
} | ||
defer conn.Close() | ||
|
||
c.log.Debug("Getting manifest") | ||
|
||
client := userapi.NewUserAPIClient(conn) | ||
resp, err := client.GetManifests(ctx, &userapi.GetManifestsRequest{}) | ||
if err != nil { | ||
return GetManifestsResponse{}, fmt.Errorf("getting manifests: %w", err) | ||
} | ||
|
||
return GetManifestsResponse{ | ||
Manifests: resp.Manifests, | ||
Policies: resp.Policies, | ||
RootCA: resp.RootCA, | ||
MeshCA: resp.MeshCA, | ||
}, nil | ||
} | ||
|
||
// GetManifestsResponse contains the Coordinator's response to a GetManifests call. | ||
type GetManifestsResponse struct { | ||
Manifests [][]byte | ||
Policies [][]byte | ||
// PEM-encoded certificate | ||
RootCA []byte | ||
// PEM-encoded certificate | ||
MeshCA []byte | ||
} |