Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Entitymanager and populate security artifacts #12

Merged
merged 14 commits into from
Aug 23, 2023
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.21

require (
github.com/golang/glog v1.1.2
github.com/google/go-cmp v0.5.9
github.com/openconfig/gnmi v0.0.0-20220617175856-41246b1b3507
github.com/openconfig/gnsi v1.2.1
go.mozilla.org/pkcs7 v0.0.0-20210826202110-33d05740a352
Expand Down
144 changes: 130 additions & 14 deletions server/entitymanager/entitymanager.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,154 @@
package entitymanager

import (
"crypto"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"

"github.com/openconfig/bootz/proto/bootz"
"github.com/openconfig/bootz/server/service"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"google.golang.org/protobuf/proto"

log "github.com/golang/glog"
)

// InMemoryEntityManager provides a simple in memory handler
// for Entities.
type InMemoryEntityManager struct{}
type InMemoryEntityManager struct {
gmacf marked this conversation as resolved.
Show resolved Hide resolved
// inventory represents an organization's inventory of owned chassis.
chassisInventory map[service.EntityLookup]*service.ChassisEntity
// represents the current status of known control cards
controlCardStatuses map[string]bootz.ControlCardState_ControlCardStatus
artifacts *service.SecurityArtifacts
}

// ResolveChassis returns an entity based on the provided lookup.
func (m *InMemoryEntityManager) ResolveChassis(lookup *service.EntityLookup) (*service.ChassisEntity, error) {
return nil, status.Errorf(codes.Unimplemented, "Unimplemented")
if e, ok := m.chassisInventory[*lookup]; ok {
return e, nil
}

return nil, status.Errorf(codes.NotFound, "chassis %+v not found in inventory", *lookup)
}

func (m *InMemoryEntityManager) GetBootstrapData(c *bootz.ControlCard) (*bootz.BootstrapDataResponse, error) {
// First check if we are expecting this control card.
if c.SerialNumber == "" {
return nil, status.Errorf(codes.InvalidArgument, "no serial number provided")
}
if _, ok := m.controlCardStatuses[c.GetSerialNumber()]; !ok {
return nil, status.Errorf(codes.NotFound, "control card %v not found in inventory", c.GetSerialNumber())
}
// Construct the response. This emulator hardcodes these values but a real Bootz server would not.
// TODO: Populate these placeholders with realistic ones.
return &bootz.BootstrapDataResponse{
SerialNum: c.SerialNumber,
IntendedImage: &bootz.SoftwareImage{
Name: "Default Image",
Version: "1.0",
Url: "https://path/to/image",
OsImageHash: "ABCDEF",
HashAlgorithm: "SHA256",
},
BootPasswordHash: "ABCD123",
ServerTrustCert: "FakeTLSCert",
BootConfig: &bootz.BootConfig{
VendorConfig: []byte("Vendor Config"),
OcConfig: []byte("OC Config"),
},
Credentials: &bootz.Credentials{},
// TODO: Populate pathz, authz and certificates.
}, nil
}

func (m *InMemoryEntityManager) SetStatus(req *bootz.ReportStatusRequest) error {
if len(req.GetStates()) == 0 {
return status.Errorf(codes.InvalidArgument, "no control card states provided")
}
log.Infof("Bootstrap Status: %v: Status message: %v", req.GetStatus(), req.GetStatusMessage())

for _, c := range req.GetStates() {
previousStatus, ok := m.controlCardStatuses[c.GetSerialNumber()]
if !ok {
return status.Errorf(codes.NotFound, "control card %v not found in inventory", c.GetSerialNumber())
}
log.Infof("control card %v changed status from %v to %v", c.GetSerialNumber(), previousStatus, c.GetStatus())
m.controlCardStatuses[c.GetSerialNumber()] = c.GetStatus()
}
return nil
}

// Sign unmarshals the SignedResponse bytes then generates a signature from its Ownership Certificate private key.
func (m *InMemoryEntityManager) Sign(resp *bootz.GetBootstrapDataResponse, serial string) error {
// Sign the response
gmacf marked this conversation as resolved.
Show resolved Hide resolved
block, _ := pem.Decode([]byte(m.artifacts.OC.Key))
if block == nil {
return status.Errorf(codes.Internal, "unable to decode OC private key")
gmacf marked this conversation as resolved.
Show resolved Hide resolved
}
priv, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return err
}
if resp.GetSignedResponse() == nil {
return status.Errorf(codes.InvalidArgument, "empty signed response")
}
signedResponseBytes, err := proto.Marshal(resp.GetSignedResponse())
if err != nil {
return err
}
hashed := sha256.Sum256(signedResponseBytes)
sig, err := rsa.SignPKCS1v15(nil, priv, crypto.SHA256, hashed[:])
gmacf marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
resp.ResponseSignature = string(sig)
// Populate the OV
ov, err := m.FetchOwnershipVoucher(serial)
if err != nil {
return err
}
resp.OwnershipVoucher = []byte(ov)
// Populate the OC
resp.OwnershipCertificate = []byte(m.artifacts.OC.Cert)
return nil
}

// GetBootstrapData returns the Bootstrap data for the provided control card.
func (m *InMemoryEntityManager) GetBootstrapData(*bootz.ControlCard) (*bootz.BootstrapDataResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "Unimplemented")
// FetchOwnershipVoucher retrieves the ownership voucher for a control card
func (m *InMemoryEntityManager) FetchOwnershipVoucher(serial string) (string, error) {
if ov, ok := m.artifacts.OV[serial]; ok {
return ov, nil
}
return "", status.Errorf(codes.NotFound, "OV for serial %v not found", serial)
}

// SetStatus returns the current status based on the status request.
func (m *InMemoryEntityManager) SetStatus(*bootz.ReportStatusRequest) error {
return status.Errorf(codes.Unimplemented, "Unimplemented")
// AddControlCard adds a new control card to the entity manager.
func (m *InMemoryEntityManager) AddControlCard(serial string) *InMemoryEntityManager {
m.controlCardStatuses[serial] = bootz.ControlCardState_CONTROL_CARD_STATUS_UNSPECIFIED
return m
}

// Sign populates the signing fields of the provided Bootstrap data response.
// If fields are set they will be overwritten.
func (m *InMemoryEntityManager) Sign(*bootz.GetBootstrapDataResponse) error {
return status.Errorf(codes.Unimplemented, "Unimplemented")
// AddChassis adds a new chassis to the entity manager.
func (m *InMemoryEntityManager) AddChassis(bootMode bootz.BootMode, manufacturer string, serial string) *InMemoryEntityManager {
l := service.EntityLookup{
Manufacturer: manufacturer,
SerialNumber: serial,
}
e := service.ChassisEntity{
gmacf marked this conversation as resolved.
Show resolved Hide resolved
BootMode: bootMode,
}
m.chassisInventory[l] = &e
return m
}

// New returns a new in-memory entity manager.
func New() *InMemoryEntityManager {
return &InMemoryEntityManager{}
func New(artifacts *service.SecurityArtifacts) *InMemoryEntityManager {
gmacf marked this conversation as resolved.
Show resolved Hide resolved
return &InMemoryEntityManager{
artifacts: artifacts,
chassisInventory: make(map[service.EntityLookup]*service.ChassisEntity),
controlCardStatuses: make(map[string]bootz.ControlCardState_ControlCardStatus),
}
}
Loading