mirror of
https://github.com/matter-labs/vault-auth-tee.git
synced 2025-07-20 15:23:56 +02:00

Add the ability to clear the `sgx_mrsigner` and `sgx_mrenclave` field. Otherwise we cannot switch from `sgx_mrenclave` to `sgx_mrsigner` based authentication. Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
333 lines
9.3 KiB
Go
333 lines
9.3 KiB
Go
// SPDX-License-Identifier: MPL-2.0
|
|
// Copyright (c) HashiCorp, Inc.
|
|
// Copyright (c) Matter Labs
|
|
|
|
package vault_auth_tee
|
|
|
|
import "C"
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/hashicorp/vault/sdk/framework"
|
|
"github.com/hashicorp/vault/sdk/helper/tokenutil"
|
|
"github.com/hashicorp/vault/sdk/logical"
|
|
)
|
|
|
|
func pathListTees(b *backend) *framework.Path {
|
|
return &framework.Path{
|
|
Pattern: "tees/?",
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: operationPrefixTee,
|
|
OperationSuffix: "tees",
|
|
Navigation: true,
|
|
ItemType: "Tee",
|
|
},
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
logical.ListOperation: b.pathTeeList,
|
|
},
|
|
|
|
HelpSynopsis: pathTeeHelpSyn,
|
|
HelpDescription: pathTeeHelpDesc,
|
|
}
|
|
}
|
|
|
|
func pathTees(b *backend) *framework.Path {
|
|
p := &framework.Path{
|
|
Pattern: "tees/" + framework.GenericNameRegex("name"),
|
|
|
|
DisplayAttrs: &framework.DisplayAttributes{
|
|
OperationPrefix: operationPrefixTee,
|
|
OperationSuffix: "tee",
|
|
Action: "Create",
|
|
ItemType: "Tee",
|
|
},
|
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
"name": {
|
|
Type: framework.TypeString,
|
|
Description: "The name of the Tee, which passes remote attestation verification",
|
|
},
|
|
|
|
"types": {
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: "The types of the TEE.",
|
|
},
|
|
|
|
"sgx_mrsigner": {
|
|
Type: framework.TypeString,
|
|
Description: `The SGX mrsigner hex value to check the attestation report against`,
|
|
},
|
|
|
|
"sgx_mrenclave": {
|
|
Type: framework.TypeString,
|
|
Description: `The SGX mrenclave hex value to check the attestation report against`,
|
|
},
|
|
|
|
"sgx_isv_prodid": {
|
|
Type: framework.TypeInt,
|
|
Description: `The SGX isv_prodid value to check the attestation report against`,
|
|
},
|
|
|
|
"sgx_min_isv_svn": {
|
|
Type: framework.TypeInt,
|
|
Description: `The SGX minimum isv_svn value to check the attestation report against`,
|
|
},
|
|
|
|
"sgx_allowed_tcb_levels": {
|
|
Type: framework.TypeCommaStringSlice,
|
|
Description: `A comma seperated list of allowed SGX TCB states.
|
|
Allowed values are: ConfigNeeded, OutOfDate, OutOfDateConfigNeeded, SwHardeningNeeded, ConfigAndSwHardeningNeeded`,
|
|
},
|
|
|
|
"display_name": {
|
|
Type: framework.TypeString,
|
|
Description: `The display name to use for clients using this certificate.`,
|
|
},
|
|
},
|
|
|
|
Callbacks: map[logical.Operation]framework.OperationFunc{
|
|
logical.DeleteOperation: b.pathTeeDelete,
|
|
logical.ReadOperation: b.pathTeeRead,
|
|
logical.UpdateOperation: b.pathTeeWrite,
|
|
},
|
|
|
|
HelpSynopsis: pathTeeHelpSyn,
|
|
HelpDescription: pathTeeHelpDesc,
|
|
}
|
|
|
|
tokenutil.AddTokenFields(p.Fields)
|
|
return p
|
|
}
|
|
|
|
func (b *backend) Tee(ctx context.Context, s logical.Storage, n string) (*TeeEntry, error) {
|
|
entry, err := s.Get(ctx, "tee/"+strings.ToLower(n))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if entry == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
var result TeeEntry
|
|
if err := entry.DecodeJSON(&result); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &result, nil
|
|
}
|
|
|
|
func (b *backend) pathTeeDelete(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
err := req.Storage.Delete(ctx, "tee/"+strings.ToLower(d.Get("name").(string)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
func (b *backend) pathTeeList(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
tees, err := req.Storage.List(ctx, "tee/")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return logical.ListResponse(tees), nil
|
|
}
|
|
|
|
func (b *backend) pathTeeRead(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
tee, err := b.Tee(ctx, req.Storage, strings.ToLower(d.Get("name").(string)))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if tee == nil {
|
|
return nil, nil
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"display_name": tee.DisplayName,
|
|
"types": tee.Types,
|
|
"sgx_mrsigner": tee.SgxMrsigner,
|
|
"sgx_mrenclave": tee.SgxMrenclave,
|
|
"sgx_isv_prodid": tee.SgxIsvProdid,
|
|
"sgx_min_isv_svn": tee.SgxMinIsvSvn,
|
|
"sgx_allowed_tcb_levels": tee.SgxAllowedTcbLevels,
|
|
}
|
|
tee.PopulateTokenData(data)
|
|
|
|
return &logical.Response{
|
|
Data: data,
|
|
}, nil
|
|
}
|
|
|
|
func (b *backend) pathTeeWrite(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
name := strings.ToLower(d.Get("name").(string))
|
|
|
|
tee, err := b.Tee(ctx, req.Storage, name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if tee == nil {
|
|
tee = &TeeEntry{
|
|
Name: name,
|
|
}
|
|
}
|
|
|
|
// Get non tokenutil fields
|
|
if displayNameRaw, ok := d.GetOk("display_name"); ok {
|
|
tee.DisplayName = displayNameRaw.(string)
|
|
}
|
|
|
|
if teeTypes, ok := d.GetOk("types"); ok {
|
|
tee.Types = make(map[string]bool)
|
|
handled := make(map[string]bool)
|
|
for _, t := range teeTypes.([]string) {
|
|
// only SGX supported for now
|
|
if _, ok = handled[t]; ok {
|
|
return logical.ErrorResponse(fmt.Sprintf("duplicate TEE type `%s`", t)), nil
|
|
}
|
|
if t == "sgx" {
|
|
tee.Types[t] = true
|
|
handled[t] = true
|
|
response, err := handleSGXConfig(d, tee)
|
|
if response != nil || err != nil {
|
|
return response, err
|
|
}
|
|
} else {
|
|
return logical.ErrorResponse(fmt.Sprintf("invalid TEE type `%s`", t)), nil
|
|
}
|
|
}
|
|
} else {
|
|
return logical.ErrorResponse("missing TEE types"), nil
|
|
}
|
|
|
|
// Get tokenutil fields
|
|
if err := tee.ParseTokenFields(req, d); err != nil {
|
|
return logical.ErrorResponse(err.Error()), logical.ErrInvalidRequest
|
|
}
|
|
|
|
var resp logical.Response
|
|
|
|
systemDefaultTTL := b.System().DefaultLeaseTTL()
|
|
if tee.TokenTTL > systemDefaultTTL {
|
|
resp.AddWarning(fmt.Sprintf("Given ttl of %d seconds is greater than current mount/system default of %d seconds", tee.TokenTTL/time.Second, systemDefaultTTL/time.Second))
|
|
}
|
|
systemMaxTTL := b.System().MaxLeaseTTL()
|
|
if tee.TokenMaxTTL > systemMaxTTL {
|
|
resp.AddWarning(fmt.Sprintf("Given max_ttl of %d seconds is greater than current mount/system default of %d seconds", tee.TokenMaxTTL/time.Second, systemMaxTTL/time.Second))
|
|
}
|
|
if tee.TokenMaxTTL != 0 && tee.TokenTTL > tee.TokenMaxTTL {
|
|
return logical.ErrorResponse("ttl should be shorter than max_ttl"), nil
|
|
}
|
|
if tee.TokenPeriod > systemMaxTTL {
|
|
resp.AddWarning(fmt.Sprintf("Given period of %d seconds is greater than the backend's maximum TTL of %d seconds", tee.TokenPeriod/time.Second, systemMaxTTL/time.Second))
|
|
}
|
|
|
|
// Default the display name to the certificate name if not given
|
|
if tee.DisplayName == "" {
|
|
tee.DisplayName = name
|
|
}
|
|
|
|
// Store it
|
|
entry, err := logical.StorageEntryJSON("tee/"+name, tee)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := req.Storage.Put(ctx, entry); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(resp.Warnings) == 0 {
|
|
return nil, nil
|
|
}
|
|
|
|
return &resp, nil
|
|
}
|
|
|
|
func handleSGXConfig(d *framework.FieldData, tee *TeeEntry) (*logical.Response, error) {
|
|
if sgxMrsignerRaw, ok := d.GetOk("sgx_mrsigner"); ok {
|
|
tee.SgxMrsigner = strings.ToLower(sgxMrsignerRaw.(string))
|
|
if tee.SgxMrsigner != "" {
|
|
b, err := hex.DecodeString(tee.SgxMrsigner)
|
|
if err != nil || len(b) != 32 {
|
|
return logical.ErrorResponse("`sgx_mrsigner` must be 32 byte hex encoded"), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if sgxMrenclaveRaw, ok := d.GetOk("sgx_mrenclave"); ok {
|
|
tee.SgxMrenclave = strings.ToLower(sgxMrenclaveRaw.(string))
|
|
if tee.SgxMrenclave != "" {
|
|
b, err := hex.DecodeString(tee.SgxMrenclave)
|
|
if err != nil || len(b) != 32 {
|
|
return logical.ErrorResponse("`sgx_mrenclave` must be 32 byte hex encoded"), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
if tee.SgxMrsigner == "" && tee.SgxMrenclave == "" {
|
|
return logical.ErrorResponse("either `sgx_mrsigner` or `sgx_mrenclave` must be set"), nil
|
|
}
|
|
|
|
if sgxIsvProdidRaw, ok := d.GetOk("sgx_isv_prodid"); ok {
|
|
tee.SgxIsvProdid = sgxIsvProdidRaw.(int)
|
|
}
|
|
|
|
if sgxMinIsvSvnRaw, ok := d.GetOk("sgx_min_isv_svn"); ok {
|
|
tee.SgxMinIsvSvn = sgxMinIsvSvnRaw.(int)
|
|
}
|
|
|
|
if sgxAllowedTcbLevelsRaw, ok := d.GetOk("sgx_allowed_tcb_levels"); ok {
|
|
tee.SgxAllowedTcbLevels = make(map[SgxQlQvResult]bool)
|
|
for _, v := range sgxAllowedTcbLevelsRaw.([]string) {
|
|
var state SgxQlQvResult
|
|
switch v {
|
|
case "Ok":
|
|
state = SgxQlQvResultOk
|
|
case "ConfigNeeded":
|
|
state = SgxQlQvResultConfigNeeded
|
|
case "OutOfDate":
|
|
state = SgxQlQvResultOutOfDate
|
|
case "OutOfDateConfigNeeded":
|
|
state = SgxQlQvResultOutOfDateConfigNeeded
|
|
case "SwHardeningNeeded":
|
|
state = SgxQlQvResultSwHardeningNeeded
|
|
case "ConfigAndSwHardeningNeeded":
|
|
state = SgxQlQvResultConfigAndSwHardeningNeeded
|
|
default:
|
|
return logical.ErrorResponse("invalid sgx_allowed_tcb_levels value"), logical.ErrInvalidRequest
|
|
}
|
|
tee.SgxAllowedTcbLevels[state] = true
|
|
}
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
type TeeEntry struct {
|
|
tokenutil.TokenParams
|
|
|
|
Name string
|
|
DisplayName string
|
|
Types map[string]bool
|
|
SgxMrsigner string
|
|
SgxMrenclave string
|
|
SgxIsvProdid int
|
|
SgxMinIsvSvn int
|
|
SgxAllowedTcbLevels map[SgxQlQvResult]bool
|
|
}
|
|
|
|
const pathTeeHelpSyn = `
|
|
Manage TEE remote attestation parameters used for authentication.`
|
|
|
|
const pathTeeHelpDesc = `
|
|
This endpoint allows you to create, read, update, and delete TEEs
|
|
that are allowed to authenticate.
|
|
|
|
Deleting a TEE will not revoke auth for prior authenticated connections.
|
|
To do this, do a revoke on "login". If you don't need to revoke login immediately,
|
|
then the next renew will cause the lease to expire.
|
|
`
|