Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion launcher/agent/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,18 @@

type principalIDTokenFetcher func(audience string) ([][]byte, error)

// VerifyMethod represents the possible atrestation verification methods.
type VerifyMethod string

const (
// VerifyUnset refers to an unspecified method.
VerifyUnset VerifyMethod = "UNSET"
// VerifyConfidentialSpaceMethod refers to VerifyConfidentialSpace.
VerifyConfidentialSpaceMethod VerifyMethod = "VerifyConfidentialSpace"
// VerifyAttestationMethod refers to VerifyAttestation.
VerifyAttestationMethod VerifyMethod = "VerifyAttestation"
)

// AttestationAgent is an agent that interacts with GCE's Attestation Service
// to Verify an attestation message. It is an interface instead of a concrete
// struct to make testing easier.
Expand All @@ -67,6 +79,7 @@
// VerifyAttestation API
type AttestAgentOpts struct {
TokenOptions *models.TokenOptions
Method VerifyMethod
}

type agent struct {
Expand Down Expand Up @@ -251,16 +264,34 @@
a.logger.Info("Found container image signatures: %v\n", signatures)
}

resp, err := client.VerifyAttestation(ctx, req)
resp, err := a.verify(ctx, req, client, opts)
if err != nil {
return nil, err
}

if len(resp.PartialErrs) > 0 {
a.logger.Error(fmt.Sprintf("Partial errors from VerifyAttestation: %v", resp.PartialErrs))
}
return resp.ClaimsToken, nil
}

func (a *agent) verify(ctx context.Context, req verifier.VerifyAttestationRequest, client verifier.Client, opts AttestAgentOpts) (*verifier.VerifyAttestationResponse, error) {

Check warning on line 278 in launcher/agent/agent.go

View workflow job for this annotation

GitHub Actions / Lint ./launcher (ubuntu-latest, Go 1.21.x)

unused-parameter: parameter 'opts' seems to be unused, consider removing or renaming it as _ (revive)
// If not specified in opts, use experiment to determine verify method.
// method := opts.Method
// if method == VerifyUnset {
// if a.launchSpec.Experiments.EnableVerifyCS {
// method = VerifyConfidentialSpaceMethod
// } else {
// method = VerifyAttestationMethod
// }
// }

// if method == VerifyConfidentialSpaceMethod {
return client.VerifyConfidentialSpace(ctx, req)
// }
// return client.VerifyAttestation(ctx, req)
}

func convertOCIToContainerSignature(ociSig oci.Signature) (*verifier.ContainerSignature, error) {
payload, err := ociSig.Payload()
if err != nil {
Expand Down
102 changes: 102 additions & 0 deletions launcher/agent/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"crypto/rand"
"crypto/rsa"
"encoding/base64"
"errors"
"fmt"
"math"
"runtime"
Expand All @@ -18,6 +19,7 @@ import (
"github.com/google/go-tpm-tools/cel"
"github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm-tools/internal/test"
"github.com/google/go-tpm-tools/launcher/internal/experiments"
"github.com/google/go-tpm-tools/launcher/internal/logging"
"github.com/google/go-tpm-tools/launcher/internal/signaturediscovery"
"github.com/google/go-tpm-tools/launcher/spec"
Expand Down Expand Up @@ -638,3 +640,103 @@ func measureFakeEvents(attestAgent AttestationAgent) error {
}
return nil
}

type testClient struct {
verifyCSResp *verifier.VerifyAttestationResponse
verifyAttResp *verifier.VerifyAttestationResponse
}

func (t *testClient) CreateChallenge(_ context.Context) (*verifier.Challenge, error) {
return nil, errors.New("unimplemented")
}

func (t *testClient) VerifyAttestation(_ context.Context, _ verifier.VerifyAttestationRequest) (*verifier.VerifyAttestationResponse, error) {
return t.verifyAttResp, nil
}

func (t *testClient) VerifyConfidentialSpace(_ context.Context, _ verifier.VerifyAttestationRequest) (*verifier.VerifyAttestationResponse, error) {
return t.verifyCSResp, nil
}

func TestVerify(t *testing.T) {
expectedCSResp := &verifier.VerifyAttestationResponse{
ClaimsToken: []byte("verify-cs-token"),
}
expectedAttResp := &verifier.VerifyAttestationResponse{
ClaimsToken: []byte("verify-att-token"),
}

vClient := &testClient{
verifyCSResp: expectedCSResp,
verifyAttResp: expectedAttResp,
}

testcases := []struct {
name string
opts AttestAgentOpts
exps experiments.Experiments
expectedResp *verifier.VerifyAttestationResponse
}{
{
name: "VerifyCS in options",
opts: AttestAgentOpts{
Method: VerifyConfidentialSpaceMethod,
},
exps: experiments.Experiments{
EnableVerifyCS: false,
},
expectedResp: expectedCSResp,
},
{
name: "VerifyAtt in options",
opts: AttestAgentOpts{
Method: VerifyAttestationMethod,
},
exps: experiments.Experiments{
EnableVerifyCS: true,
},
expectedResp: expectedAttResp,
},
{
name: "VerifyCS in experiment",
opts: AttestAgentOpts{
Method: VerifyUnset,
},
exps: experiments.Experiments{
EnableVerifyCS: true,
},
expectedResp: expectedCSResp,
},
{
name: "VerifyAtt in experiment",
opts: AttestAgentOpts{
Method: VerifyUnset,
},
exps: experiments.Experiments{
EnableVerifyCS: false,
},
expectedResp: expectedAttResp,
},
}

ctx := context.Background()

for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
attAgent := agent{
launchSpec: spec.LaunchSpec{
Experiments: tc.exps,
},
}

resp, err := attAgent.verify(ctx, verifier.VerifyAttestationRequest{}, vClient, tc.opts)
if err != nil {
t.Fatalf("verify() returned failure: %v", err)
}

if diff := cmp.Diff(resp, tc.expectedResp); diff != "" {
t.Errorf("verify() did not return expected response (-got, +want): %v", diff)
}
})
}
}
40 changes: 39 additions & 1 deletion launcher/teeserver/tee_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
const (
gcaEndpoint = "/v1/token"
itaEndpoint = "/v1/intel/token"

verifyMethodHeader = "Verify-Method"
)

var clientErrorCodes = map[codes.Code]struct{}{
Expand Down Expand Up @@ -133,15 +135,50 @@ func (a *attestHandler) getITAToken(w http.ResponseWriter, r *http.Request) {
a.attest(w, r, a.clients.ITA)
}

func (a *attestHandler) parseVerifyMethod(headers http.Header) agent.VerifyMethod {
if headers == nil {
a.logger.Info("No headers specified in request.")
return agent.VerifyUnset
}

methods, ok := headers[verifyMethodHeader]
if !ok {
a.logger.Info("No VerifyMethod specified in request.")
return agent.VerifyUnset
}

if len(methods) != 1 {
a.logger.Warn(fmt.Sprintf("Unexpected number of values in VerifyMethod header: %d, expect 1", len(methods)))
return agent.VerifyUnset
}

switch methods[0] {
case string(agent.VerifyConfidentialSpaceMethod):
a.logger.Info("Parsed VerifyMethod: %v", agent.VerifyConfidentialSpaceMethod)
return agent.VerifyConfidentialSpaceMethod
case string(agent.VerifyAttestationMethod):
a.logger.Info("Parsed VerifyMethod: %v", agent.VerifyAttestationMethod)
return agent.VerifyAttestationMethod
default:
a.logger.Warn(fmt.Sprintf("Unsupported VerifyMethod: %v", methods[0]))
}

return agent.VerifyUnset
}

func (a *attestHandler) attest(w http.ResponseWriter, r *http.Request, client verifier.Client) {
verifyMethod := a.parseVerifyMethod(r.Header)

switch r.Method {
case http.MethodGet:
if err := a.attestAgent.Refresh(a.ctx); err != nil {
a.logAndWriteHTTPError(w, http.StatusInternalServerError, fmt.Errorf("failed to refresh attestation agent: %w", err))
return
}

token, err := a.attestAgent.AttestWithClient(a.ctx, agent.AttestAgentOpts{}, client)
token, err := a.attestAgent.AttestWithClient(a.ctx, agent.AttestAgentOpts{
Method: verifyMethod,
}, client)
if err != nil {
a.handleAttestError(w, err, "failed to retrieve attestation service token")
return
Expand Down Expand Up @@ -178,6 +215,7 @@ func (a *attestHandler) attest(w http.ResponseWriter, r *http.Request, client ve
// Do not check that TokenTypeOptions matches TokenType in the launcher.
opts := agent.AttestAgentOpts{
TokenOptions: &tokenOptions,
Method: verifyMethod,
}
tok, err := a.attestAgent.AttestWithClient(a.ctx, opts, client)
if err != nil {
Expand Down
Loading
Loading