2022-09-05 03:06:08 -04:00
|
|
|
/*
|
|
|
|
Copyright (c) Edgeless Systems GmbH
|
|
|
|
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
*/
|
|
|
|
|
2022-06-29 10:13:01 -04:00
|
|
|
package watcher
|
2022-05-23 05:36:54 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"encoding/asn1"
|
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2022-06-15 10:00:48 -04:00
|
|
|
"path/filepath"
|
2022-05-23 05:36:54 -04:00
|
|
|
"sync"
|
|
|
|
"testing"
|
|
|
|
|
2022-09-21 07:47:57 -04:00
|
|
|
"github.com/edgelesssys/constellation/v2/internal/atls"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/constants"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/file"
|
|
|
|
"github.com/edgelesssys/constellation/v2/internal/logger"
|
2022-05-23 05:36:54 -04:00
|
|
|
"github.com/spf13/afero"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/stretchr/testify/require"
|
2022-06-30 09:24:36 -04:00
|
|
|
"go.uber.org/goleak"
|
2022-05-23 05:36:54 -04:00
|
|
|
)
|
|
|
|
|
2022-06-30 09:24:36 -04:00
|
|
|
func TestMain(m *testing.M) {
|
|
|
|
goleak.VerifyTestMain(m,
|
|
|
|
// https://github.com/census-instrumentation/opencensus-go/issues/1262
|
|
|
|
goleak.IgnoreTopFunction("go.opencensus.io/stats/view.(*worker).start"),
|
2022-10-28 11:57:24 -04:00
|
|
|
// https://github.com/google/go-sev-guest/issues/23
|
|
|
|
goleak.IgnoreTopFunction("github.com/golang/glog.(*loggingT).flushDaemon"),
|
2022-06-30 09:24:36 -04:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-05-23 05:36:54 -04:00
|
|
|
func TestNewUpdateableValidator(t *testing.T) {
|
|
|
|
testCases := map[string]struct {
|
|
|
|
provider string
|
|
|
|
writeFile bool
|
|
|
|
wantErr bool
|
|
|
|
}{
|
|
|
|
"azure": {
|
|
|
|
provider: "azure",
|
|
|
|
writeFile: true,
|
|
|
|
},
|
|
|
|
"gcp": {
|
|
|
|
provider: "gcp",
|
|
|
|
writeFile: true,
|
|
|
|
},
|
|
|
|
"qemu": {
|
|
|
|
provider: "qemu",
|
|
|
|
writeFile: true,
|
|
|
|
},
|
|
|
|
"no file": {
|
|
|
|
provider: "azure",
|
|
|
|
writeFile: false,
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
"invalid provider": {
|
|
|
|
provider: "invalid",
|
|
|
|
writeFile: true,
|
|
|
|
wantErr: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for name, tc := range testCases {
|
|
|
|
t.Run(name, func(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
handler := file.NewHandler(afero.NewMemMapFs())
|
|
|
|
if tc.writeFile {
|
|
|
|
require.NoError(handler.WriteJSON(
|
2022-06-29 10:13:01 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
2022-05-23 05:36:54 -04:00
|
|
|
map[uint32][]byte{
|
|
|
|
11: {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
|
|
|
},
|
2022-08-12 09:59:45 -04:00
|
|
|
))
|
|
|
|
require.NoError(handler.WriteJSON(
|
|
|
|
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
|
|
|
|
[]uint32{11},
|
2022-05-23 05:36:54 -04:00
|
|
|
))
|
2022-08-29 10:41:09 -04:00
|
|
|
require.NoError(handler.Write(
|
2022-10-05 09:02:46 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
|
2022-08-29 10:41:09 -04:00
|
|
|
[]byte{},
|
|
|
|
))
|
|
|
|
require.NoError(handler.Write(
|
2022-10-05 09:02:46 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
|
2022-08-29 10:41:09 -04:00
|
|
|
[]byte("false"),
|
|
|
|
))
|
2022-08-31 14:10:49 -04:00
|
|
|
require.NoError(handler.Write(
|
|
|
|
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
|
|
|
|
[]byte("true"),
|
|
|
|
))
|
2022-05-23 05:36:54 -04:00
|
|
|
}
|
|
|
|
|
2022-06-29 10:13:01 -04:00
|
|
|
_, err := NewValidator(
|
2022-06-28 10:51:30 -04:00
|
|
|
logger.NewTest(t),
|
|
|
|
tc.provider,
|
|
|
|
handler,
|
2022-08-31 14:10:49 -04:00
|
|
|
false,
|
2022-06-28 10:51:30 -04:00
|
|
|
)
|
2022-05-23 05:36:54 -04:00
|
|
|
if tc.wantErr {
|
|
|
|
assert.Error(err)
|
|
|
|
} else {
|
|
|
|
assert.NoError(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUpdate(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
oid := fakeOID{1, 3, 9900, 1}
|
2022-08-29 10:41:09 -04:00
|
|
|
newValidator := func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
|
2022-05-23 05:36:54 -04:00
|
|
|
return fakeValidator{fakeOID: oid}
|
|
|
|
}
|
|
|
|
handler := file.NewHandler(afero.NewMemMapFs())
|
|
|
|
|
|
|
|
// create server
|
2022-06-28 10:51:30 -04:00
|
|
|
validator := &Updatable{
|
|
|
|
log: logger.NewTest(t),
|
|
|
|
newValidator: newValidator,
|
|
|
|
fileHandler: handler,
|
|
|
|
}
|
2022-05-23 05:36:54 -04:00
|
|
|
|
|
|
|
// Update should fail if the file does not exist
|
|
|
|
assert.Error(validator.Update())
|
|
|
|
|
|
|
|
// write measurement config
|
|
|
|
require.NoError(handler.WriteJSON(
|
2022-06-29 10:13:01 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
2022-05-23 05:36:54 -04:00
|
|
|
map[uint32][]byte{
|
|
|
|
11: {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
|
|
|
},
|
|
|
|
file.OptNone,
|
|
|
|
))
|
2022-08-12 09:59:45 -04:00
|
|
|
require.NoError(handler.WriteJSON(
|
|
|
|
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
|
|
|
|
[]uint32{11},
|
|
|
|
))
|
2022-08-29 10:41:09 -04:00
|
|
|
require.NoError(handler.Write(
|
2022-10-05 09:02:46 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
|
2022-08-29 10:41:09 -04:00
|
|
|
[]byte{},
|
|
|
|
))
|
|
|
|
require.NoError(handler.Write(
|
2022-10-05 09:02:46 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
|
2022-08-29 10:41:09 -04:00
|
|
|
[]byte("false"),
|
|
|
|
))
|
2022-08-31 14:10:49 -04:00
|
|
|
require.NoError(handler.Write(
|
|
|
|
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
|
|
|
|
[]byte("true"),
|
|
|
|
))
|
2022-05-23 05:36:54 -04:00
|
|
|
|
|
|
|
// call update once to initialize the server's validator
|
|
|
|
require.NoError(validator.Update())
|
|
|
|
|
|
|
|
// create tls config and start the server
|
|
|
|
serverConfig, err := atls.CreateAttestationServerTLSConfig(nil, []atls.Validator{validator})
|
|
|
|
require.NoError(err)
|
|
|
|
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
|
|
_, _ = io.WriteString(w, "hello")
|
|
|
|
}))
|
|
|
|
server.TLS = serverConfig
|
|
|
|
server.StartTLS()
|
|
|
|
defer server.Close()
|
|
|
|
|
|
|
|
// test connection to server
|
|
|
|
clientOID := fakeOID{1, 3, 9900, 1}
|
|
|
|
resp, err := testConnection(require, server.URL, clientOID)
|
|
|
|
require.NoError(err)
|
|
|
|
defer resp.Body.Close()
|
|
|
|
body, err := io.ReadAll(resp.Body)
|
|
|
|
require.NoError(err)
|
|
|
|
assert.EqualValues("hello", body)
|
|
|
|
|
|
|
|
// update the server's validator
|
|
|
|
oid = fakeOID{1, 3, 9900, 2}
|
|
|
|
require.NoError(validator.Update())
|
|
|
|
|
|
|
|
// client connection should fail now, since the server's validator expects a different OID from the client
|
2022-08-02 10:25:47 -04:00
|
|
|
resp, err = testConnection(require, server.URL, clientOID)
|
|
|
|
if err == nil {
|
|
|
|
defer resp.Body.Close()
|
|
|
|
}
|
2022-05-23 05:36:54 -04:00
|
|
|
assert.Error(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUpdateConcurrency(t *testing.T) {
|
|
|
|
assert := assert.New(t)
|
|
|
|
require := require.New(t)
|
|
|
|
|
|
|
|
handler := file.NewHandler(afero.NewMemMapFs())
|
|
|
|
validator := &Updatable{
|
2022-06-28 10:51:30 -04:00
|
|
|
log: logger.NewTest(t),
|
2022-05-23 05:36:54 -04:00
|
|
|
fileHandler: handler,
|
2022-08-29 10:41:09 -04:00
|
|
|
newValidator: func(m map[uint32][]byte, e []uint32, idkeydigest []byte, enforceIdKeyDigest bool, _ *logger.Logger) atls.Validator {
|
2022-05-23 05:36:54 -04:00
|
|
|
return fakeValidator{fakeOID: fakeOID{1, 3, 9900, 1}}
|
|
|
|
},
|
|
|
|
}
|
|
|
|
require.NoError(handler.WriteJSON(
|
2022-06-29 10:13:01 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.MeasurementsFilename),
|
2022-05-23 05:36:54 -04:00
|
|
|
map[uint32][]byte{
|
|
|
|
11: {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0},
|
|
|
|
},
|
|
|
|
file.OptNone,
|
|
|
|
))
|
2022-08-12 09:59:45 -04:00
|
|
|
require.NoError(handler.WriteJSON(
|
|
|
|
filepath.Join(constants.ServiceBasePath, constants.EnforcedPCRsFilename),
|
|
|
|
[]uint32{11},
|
|
|
|
))
|
2022-08-29 10:41:09 -04:00
|
|
|
require.NoError(handler.Write(
|
2022-10-05 09:02:46 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.IDKeyDigestFilename),
|
2022-08-29 10:41:09 -04:00
|
|
|
[]byte{},
|
|
|
|
))
|
|
|
|
require.NoError(handler.Write(
|
2022-10-05 09:02:46 -04:00
|
|
|
filepath.Join(constants.ServiceBasePath, constants.EnforceIDKeyDigestFilename),
|
2022-08-29 10:41:09 -04:00
|
|
|
[]byte("false"),
|
|
|
|
))
|
2022-08-31 14:10:49 -04:00
|
|
|
require.NoError(handler.Write(
|
|
|
|
filepath.Join(constants.ServiceBasePath, constants.AzureCVM),
|
|
|
|
[]byte("true"),
|
|
|
|
))
|
2022-05-23 05:36:54 -04:00
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
for i := 0; i < 10; i++ {
|
|
|
|
wg.Add(1)
|
|
|
|
go func() {
|
|
|
|
defer wg.Done()
|
|
|
|
assert.NoError(validator.Update())
|
|
|
|
}()
|
|
|
|
}
|
|
|
|
|
|
|
|
wg.Wait()
|
|
|
|
}
|
|
|
|
|
|
|
|
func testConnection(require *require.Assertions, url string, oid fakeOID) (*http.Response, error) {
|
|
|
|
clientConfig, err := atls.CreateAttestationClientTLSConfig(fakeIssuer{fakeOID: oid}, nil)
|
|
|
|
require.NoError(err)
|
|
|
|
client := http.Client{Transport: &http.Transport{TLSClientConfig: clientConfig}}
|
|
|
|
|
|
|
|
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, url, http.NoBody)
|
|
|
|
require.NoError(err)
|
|
|
|
return client.Do(req)
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeIssuer struct {
|
|
|
|
fakeOID
|
|
|
|
}
|
|
|
|
|
|
|
|
func (fakeIssuer) Issue(userData []byte, nonce []byte) ([]byte, error) {
|
|
|
|
return json.Marshal(fakeDoc{UserData: userData, Nonce: nonce})
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeValidator struct {
|
|
|
|
fakeOID
|
|
|
|
err error
|
|
|
|
}
|
|
|
|
|
|
|
|
func (v fakeValidator) Validate(attDoc []byte, nonce []byte) ([]byte, error) {
|
|
|
|
var doc fakeDoc
|
|
|
|
if err := json.Unmarshal(attDoc, &doc); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if !bytes.Equal(doc.Nonce, nonce) {
|
|
|
|
return nil, errors.New("invalid nonce")
|
|
|
|
}
|
|
|
|
return doc.UserData, v.err
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeOID asn1.ObjectIdentifier
|
|
|
|
|
|
|
|
func (o fakeOID) OID() asn1.ObjectIdentifier {
|
|
|
|
return asn1.ObjectIdentifier(o)
|
|
|
|
}
|
|
|
|
|
|
|
|
type fakeDoc struct {
|
|
|
|
UserData []byte
|
|
|
|
Nonce []byte
|
|
|
|
}
|