podman
251 строка · 7.0 Кб
1package mkcw
2
3import (
4"bufio"
5"bytes"
6"encoding/json"
7"errors"
8"fmt"
9"net/http"
10"net/url"
11"os"
12"os/exec"
13"path"
14"path/filepath"
15"strings"
16
17"github.com/containers/buildah/internal/mkcw/types"
18"github.com/containers/storage/pkg/fileutils"
19"github.com/sirupsen/logrus"
20)
21
22type (
23RegistrationRequest = types.RegistrationRequest
24TeeConfig = types.TeeConfig
25TeeConfigFlags = types.TeeConfigFlags
26TeeConfigMinFW = types.TeeConfigMinFW
27)
28
29type measurementError struct {
30err error
31}
32
33func (m measurementError) Error() string {
34return fmt.Sprintf("generating measurement for attestation: %v", m.err)
35}
36
37type attestationError struct {
38err error
39}
40
41func (a attestationError) Error() string {
42return fmt.Sprintf("registering workload: %v", a.err)
43}
44
45type httpError struct {
46statusCode int
47}
48
49func (h httpError) Error() string {
50if statusText := http.StatusText(h.statusCode); statusText != "" {
51return fmt.Sprintf("received server status %d (%q)", h.statusCode, statusText)
52}
53return fmt.Sprintf("received server status %d", h.statusCode)
54}
55
56// SendRegistrationRequest registers a workload with the specified decryption
57// passphrase with the service whose location is part of the WorkloadConfig.
58func SendRegistrationRequest(workloadConfig WorkloadConfig, diskEncryptionPassphrase, firmwareLibrary string, ignoreAttestationErrors bool, logger *logrus.Logger) error {
59if workloadConfig.AttestationURL == "" {
60return errors.New("attestation URL not provided")
61}
62
63// Measure the execution environment.
64measurement, err := GenerateMeasurement(workloadConfig, firmwareLibrary)
65if err != nil {
66if !ignoreAttestationErrors {
67return &measurementError{err}
68}
69logger.Warnf("generating measurement for attestation: %v", err)
70}
71
72// Build the workload registration (attestation) request body.
73var teeConfigBytes []byte
74switch workloadConfig.Type {
75case SEV, SEV_NO_ES, SNP:
76var cbits types.TeeConfigFlagBits
77switch workloadConfig.Type {
78case SEV:
79cbits = types.SEV_CONFIG_NO_DEBUG |
80types.SEV_CONFIG_NO_KEY_SHARING |
81types.SEV_CONFIG_ENCRYPTED_STATE |
82types.SEV_CONFIG_NO_SEND |
83types.SEV_CONFIG_DOMAIN |
84types.SEV_CONFIG_SEV
85case SEV_NO_ES:
86cbits = types.SEV_CONFIG_NO_DEBUG |
87types.SEV_CONFIG_NO_KEY_SHARING |
88types.SEV_CONFIG_NO_SEND |
89types.SEV_CONFIG_DOMAIN |
90types.SEV_CONFIG_SEV
91case SNP:
92cbits = types.SNP_CONFIG_SMT |
93types.SNP_CONFIG_MANDATORY |
94types.SNP_CONFIG_MIGRATE_MA |
95types.SNP_CONFIG_DEBUG
96default:
97panic("internal error") // shouldn't happen
98}
99teeConfig := TeeConfig{
100Flags: TeeConfigFlags{
101Bits: cbits,
102},
103MinFW: TeeConfigMinFW{
104Major: 0,
105Minor: 0,
106},
107}
108teeConfigBytes, err = json.Marshal(teeConfig)
109if err != nil {
110return err
111}
112default:
113return fmt.Errorf("don't know how to generate tee_config for %q TEEs", workloadConfig.Type)
114}
115
116registrationRequest := RegistrationRequest{
117WorkloadID: workloadConfig.WorkloadID,
118LaunchMeasurement: measurement,
119TeeConfig: string(teeConfigBytes),
120Passphrase: diskEncryptionPassphrase,
121}
122registrationRequestBytes, err := json.Marshal(registrationRequest)
123if err != nil {
124return err
125}
126
127// Register the workload.
128parsedURL, err := url.Parse(workloadConfig.AttestationURL)
129if err != nil {
130return err
131}
132parsedURL.Path = path.Join(parsedURL.Path, "/kbs/v0/register_workload")
133if err != nil {
134return err
135}
136url := parsedURL.String()
137requestContentType := "application/json"
138requestBody := bytes.NewReader(registrationRequestBytes)
139defer http.DefaultClient.CloseIdleConnections()
140resp, err := http.Post(url, requestContentType, requestBody)
141if resp != nil {
142if resp.Body != nil {
143resp.Body.Close()
144}
145switch resp.StatusCode {
146default:
147if !ignoreAttestationErrors {
148return &attestationError{&httpError{resp.StatusCode}}
149}
150logger.Warn(attestationError{&httpError{resp.StatusCode}}.Error())
151case http.StatusOK, http.StatusAccepted:
152// great!
153}
154}
155if err != nil {
156if !ignoreAttestationErrors {
157return &attestationError{err}
158}
159logger.Warn(attestationError{err}.Error())
160}
161return nil
162}
163
164// GenerateMeasurement generates the runtime measurement using the CPU count,
165// memory size, and the firmware shared library, whatever it's called, wherever
166// it is.
167// If firmwareLibrary is a path, it will be the only one checked.
168// If firmwareLibrary is a filename, it will be checked for in a hard-coded set
169// of directories.
170// If firmwareLibrary is empty, both the filename and the directory it is in
171// will be taken from a hard-coded set of candidates.
172func GenerateMeasurement(workloadConfig WorkloadConfig, firmwareLibrary string) (string, error) {
173cpuString := fmt.Sprintf("%d", workloadConfig.CPUs)
174memoryString := fmt.Sprintf("%d", workloadConfig.Memory)
175var prefix string
176switch workloadConfig.Type {
177case SEV:
178prefix = "SEV-ES"
179case SEV_NO_ES:
180prefix = "SEV"
181case SNP:
182prefix = "SNP"
183default:
184return "", fmt.Errorf("don't know which measurement to use for TEE type %q", workloadConfig.Type)
185}
186
187sharedLibraryDirs := []string{
188"/usr/local/lib64",
189"/usr/local/lib",
190"/lib64",
191"/lib",
192"/usr/lib64",
193"/usr/lib",
194}
195if llp, ok := os.LookupEnv("LD_LIBRARY_PATH"); ok {
196sharedLibraryDirs = append(sharedLibraryDirs, strings.Split(llp, ":")...)
197}
198libkrunfwNames := []string{
199"libkrunfw-sev.so.4",
200"libkrunfw-sev.so.3",
201"libkrunfw-sev.so",
202}
203var pathsToCheck []string
204if firmwareLibrary == "" {
205for _, sharedLibraryDir := range sharedLibraryDirs {
206if sharedLibraryDir == "" {
207continue
208}
209for _, libkrunfw := range libkrunfwNames {
210candidate := filepath.Join(sharedLibraryDir, libkrunfw)
211pathsToCheck = append(pathsToCheck, candidate)
212}
213}
214} else {
215if filepath.IsAbs(firmwareLibrary) {
216pathsToCheck = append(pathsToCheck, firmwareLibrary)
217} else {
218for _, sharedLibraryDir := range sharedLibraryDirs {
219if sharedLibraryDir == "" {
220continue
221}
222candidate := filepath.Join(sharedLibraryDir, firmwareLibrary)
223pathsToCheck = append(pathsToCheck, candidate)
224}
225}
226}
227for _, candidate := range pathsToCheck {
228if err := fileutils.Lexists(candidate); err == nil {
229var stdout, stderr bytes.Buffer
230logrus.Debugf("krunfw_measurement -c %s -m %s %s", cpuString, memoryString, candidate)
231cmd := exec.Command("krunfw_measurement", "-c", cpuString, "-m", memoryString, candidate)
232cmd.Stdout = &stdout
233cmd.Stderr = &stderr
234if err := cmd.Run(); err != nil {
235if stderr.Len() > 0 {
236err = fmt.Errorf("krunfw_measurement: %s: %w", strings.TrimSpace(stderr.String()), err)
237}
238return "", err
239}
240scanner := bufio.NewScanner(&stdout)
241for scanner.Scan() {
242line := scanner.Text()
243if strings.HasPrefix(line, prefix+":") {
244return strings.TrimSpace(strings.TrimPrefix(line, prefix+":")), nil
245}
246}
247return "", fmt.Errorf("generating measurement: no line starting with %q found in output from krunfw_measurement", prefix+":")
248}
249}
250return "", fmt.Errorf("generating measurement: none of %v found: %w", pathsToCheck, os.ErrNotExist)
251}
252