Skip to content

Commit

Permalink
chore: initial nitro mocking support
Browse files Browse the repository at this point in the history
chore: mocking test secretes

Pass test secretes using bin-mounted unencrypted file.

chore: working worker and own Dockerfile

Fix worker not to request TLS when connecting to redis under local
testing.

Provide own Dockerfile for local development that can also be used to
run ipython shell.

chore: ipython shell in a Docker container

Initial support for running ipython shell as a docker compose service
that connects to other services running locally.
  • Loading branch information
ibukanov committed Jul 27, 2024
1 parent bba58a6 commit addde59
Show file tree
Hide file tree
Showing 33 changed files with 711 additions and 200 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,5 @@ prepare-*response.log
nitro-shim/tools/eifbuild/third_party/**
nitro-image.eif
bat-go-repro.tar

/build/
35 changes: 28 additions & 7 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,22 +1,43 @@
FROM alpine:3.18 as sources

RUN chown nobody:nobody /srv

USER nobody

# Copy the sources first to own image separating the mod files from the rest of
# sources. This allows during the compilation to copy first only rarely changing
# module files, run go download on them and cache the downloads into a separated
# docker cache layer. The the sources are copied on top of that allowing to
# avoid module download on frequent source changes.
COPY --chown=nobody:nobody . /srv/repo
RUN mkdir /srv/mod-files && cd /srv/repo && rm -rf .git \
&& find . -name go.\* | xargs tar cf - | tar -C /srv/mod-files -xf - \
&& find . -name go.\* -delete

FROM golang:1.22-alpine as builder

# Put certs in builder image.
RUN apk update
RUN apk add -U --no-cache ca-certificates && update-ca-certificates
RUN apk add make build-base git bash

ARG VERSION
ARG BUILD_TIME
ARG COMMIT

WORKDIR /src
COPY . ./

RUN chown -R nobody:nobody /src/ && mkdir /.cache && chown -R nobody:nobody /.cache
RUN chown -R nobody:nobody /src && mkdir /.cache && chown -R nobody:nobody /.cache

USER nobody

RUN cd main && go mod download && CGO_ENABLED=0 GOOS=linux go build \
COPY --from=sources --link /srv/mod-files/ ./

RUN cd main && go mod download

COPY --from=sources --link /srv/repo ./

ARG VERSION
ARG BUILD_TIME
ARG COMMIT

RUN cd main && CGO_ENABLED=0 GOOS=linux go build \
-ldflags "-w -s -X main.version=${VERSION} -X main.buildTime=${BUILD_TIME} -X main.commit=${COMMIT}" \
-o bat-go main.go

Expand Down
11 changes: 10 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ ifdef TEST_RUN
TEST_FLAGS = --tags=$(TEST_TAGS) $(TEST_PKG) --run=$(TEST_RUN)
endif

.PHONY: all buildcmd docker test create-json-schema lint clean download-mod pcrs pcrs-only nitro-shim/tools/eifbuild/eifbuild
.PHONY: all buildcmd docker docker-local test create-json-schema lint clean download-mod pcrs pcrs-only nitro-shim/tools/eifbuild/eifbuild

all: test create-json-schema buildcmd

Expand All @@ -27,6 +27,15 @@ codeql: download-mod buildcmd
buildcmd:
cd main && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build -ldflags "-w -s -X main.version=${GIT_VERSION} -X main.buildTime=${BUILD_TIME} -X main.commit=${GIT_COMMIT}" -o ${OUTPUT}/bat-go main.go

# Create a development build for local testing. As running local container
# monitor the executable to restart on change, build first toa temporary
# location and the move to ensure tha the executable is always valid.
builddev:
test -d build || mkdir build
rm -f build/bat-go.tmp
cd main && go build -o ../build/bat-go.tmp main.go
mv build/bat-go.tmp build/bat-go

mock:
cd services && mockgen -source=./promotion/claim.go -destination=promotion/mockclaim.go -package=promotion
cd services && mockgen -source=./promotion/drain.go -destination=promotion/mockdrain.go -package=promotion
Expand Down
53 changes: 35 additions & 18 deletions libs/nitro/attestation.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ func init() {
if err != nil {
panic(err)
}
if EnclaveMocking() {
ExpectedPCR1 = mockTestPCR(1)
}
}

func ParsePCRHex(pcrHex string) (PCRBytes, error) {
Expand Down Expand Up @@ -93,6 +96,10 @@ func hashMessage(message []byte) []byte {
func Attest(ctx context.Context, nonce, message, publicKey []byte) ([]byte, error) {
messageHash := hashMessage(message)

if EnclaveMocking() {
return messageHash, nil
}

var logger = logging.Logger(ctx, "nitro.Attest")
s, err := nsm.OpenDefaultSession()
if err != nil {
Expand Down Expand Up @@ -150,24 +157,34 @@ func verifySigOnlyNotPCRs(
sig []byte,
verifyTime time.Time,
) (bool, PCRMap, error) {
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(RootAWSNitroCert))
if !ok {
return false, nil, errors.New("could not create a valid root cert pool")
}

res, err := nitrite.Verify(
sig,
nitrite.VerifyOptions{
Roots: pool,
CurrentTime: verifyTime,
},
)
if nil != err {
return false, nil, err
}
expectedHash := res.Document.UserData
pcrs := res.Document.PCRs
var expectedHash []byte
var pcrs PCRMap
if EnclaveMocking() {
pcrs = make(PCRMap)
for i := 0; i < 4; i++ {
pcrs[uint(i)] = mockTestPCR(i)
}
expectedHash = sig
} else {
pool := x509.NewCertPool()
ok := pool.AppendCertsFromPEM([]byte(RootAWSNitroCert))
if !ok {
return false, nil, errors.New("could not create a valid root cert pool")
}

res, err := nitrite.Verify(
sig,
nitrite.VerifyOptions{
Roots: pool,
CurrentTime: verifyTime,
},
)
if nil != err {
return false, nil, err
}
expectedHash = res.Document.UserData
pcrs = res.Document.PCRs
}
if len(pcrs) == 0 {
return false, nil, errors.New("failed to get PCR for the Nitro-signed document")
}
Expand Down
29 changes: 17 additions & 12 deletions libs/nitro/aws/sdk.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,7 @@ func NewAWSConfig(ctx context.Context, proxyAddr string, region string) (aws.Con
Str("region", region).
Msg("setting up new aws config")

var client http.Client
tr := nitro.NewProxyRoundTripper(ctx, proxyAddr).(*http.Transport)
tr := nitro.NewProxyTransport(ctx, proxyAddr)

certs := x509.NewCertPool()
certs.AppendCertsFromPEM([]byte(amazonRoots))
Expand All @@ -132,7 +131,7 @@ func NewAWSConfig(ctx context.Context, proxyAddr string, region string) (aws.Con
return aws.Config{}, fmt.Errorf("failed to configure transport for HTTP/2, %v", err)
}

client = http.Client{
client := &http.Client{
Transport: tr,
}

Expand All @@ -143,22 +142,28 @@ func NewAWSConfig(ctx context.Context, proxyAddr string, region string) (aws.Con
}

cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithHTTPClient(&client),
config.WithHTTPClient(client),
config.WithRegion("us-west-2"),
config.WithLogger(applicationLogger),
)
if err != nil {
return aws.Config{}, fmt.Errorf("unable to load SDK config, %v", err)
}

provider := ec2rolecreds.New(func(options *ec2rolecreds.Options) {
options.Client = imds.NewFromConfig(cfg)
})

return config.LoadDefaultConfig(context.TODO(),
config.WithHTTPClient(&client),
configOptions := []func(*config.LoadOptions) error{
config.WithHTTPClient(client),
config.WithRegion(region),
config.WithCredentialsProvider(provider),
config.WithLogger(applicationLogger),
)
}
if !nitro.EnclaveMocking() {
provider := ec2rolecreds.New(func(options *ec2rolecreds.Options) {
options.Client = imds.NewFromConfig(cfg)
})
configOptions = append(
configOptions,
config.WithCredentialsProvider(provider),
)
}

return config.LoadDefaultConfig(context.TODO(), configOptions...)
}
8 changes: 6 additions & 2 deletions libs/nitro/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package nitro

import (
"context"
"io"
"net"
"os"

Expand All @@ -17,7 +18,10 @@ type VsockWriter struct {
}

// NewVsockWriter - create a new vsock writer
func NewVsockWriter(addr string) *VsockWriter {
func NewVsockWriter(addr string) io.Writer {
if EnclaveMocking() {
return os.Stderr
}
return &VsockWriter{
socket: nil,
addr: addr,
Expand All @@ -27,7 +31,7 @@ func NewVsockWriter(addr string) *VsockWriter {
// Connect - interface implementation for connect method for VsockWriter
func (w *VsockWriter) Connect() error {
if w.socket == nil {
s, err := DialContext(context.Background(), "tcp", w.addr)
s, err := dialVsockContext(context.Background(), "tcp", w.addr)
if err != nil {
return err
}
Expand Down
37 changes: 37 additions & 0 deletions libs/nitro/mocking.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package nitro

import (
"encoding/hex"
"fmt"
"os"
"strings"
)

var enclaveMocking = os.Getenv("NITRO_ENCLAVE_MOCKING") != ""

func EnclaveMocking() bool {
return enclaveMocking
}

func mockTestPCR(pcrIndex int) []byte {
// Create arbitrary but easily recognizable values in hex
if pcrIndex < 0 || pcrIndex > 8 {
panic("Invalid mocking PCR index")
}
testHex := fmt.Sprintf("abc%d", pcrIndex)
testHex = strings.Repeat(testHex, PCRByteLength*2/4)
pcr, err := hex.DecodeString(testHex)
if err != nil {
panic(err)
}
return pcr
}

func ReadMockingSecretsFile(fileName string) string {
dir := "payment-test/secrets/"
bytes, err := os.ReadFile(dir + fileName)
if err != nil {
panic(err)
}
return string(bytes)
}
40 changes: 33 additions & 7 deletions libs/nitro/vsock.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,12 +55,12 @@ func parseVsockAddr(addr string) (uint32, uint32, error) {
}

// DialContext is a net.Dial wrapper which additionally allows connecting to vsock networks
func DialContext(ctx context.Context, network, addr string) (net.Conn, error) {
logger := logging.Logger(ctx, "nitro.DialContext")
func dialVsockContext(ctx context.Context, network, addr string) (net.Conn, error) {
logger := logging.Logger(ctx, "nitro.dialVsockContext")
logger.Debug().
Str("network", fmt.Sprintf("%v", network)).
Str("addr", fmt.Sprintf("%v", addr)).
Msg("DialContext")
Msg("dialVsockContext")

cid, port, err := parseVsockAddr(addr)
if err != nil {
Expand Down Expand Up @@ -102,12 +102,16 @@ func (p *proxyClientConfig) Proxy(*http.Request) (*url.URL, error) {
return v, err
}

// NewProxyRoundTripper returns an http.RoundTripper which routes outgoing requests through the proxy addr
func NewProxyRoundTripper(ctx context.Context, addr string) http.RoundTripper {
// NewProxyTransport returns an http.Transport which routes outgoing requests
// through the proxy addr.
func NewProxyTransport(ctx context.Context, addr string) *http.Transport {
if enclaveMocking {
return &http.Transport{}
}
config := proxyClientConfig{ctx, addr}
return &http.Transport{
Proxy: config.Proxy,
DialContext: DialContext,
DialContext: dialVsockContext,
}
}

Expand All @@ -122,7 +126,7 @@ func NewReverseProxyServer(
}
proxy := httputil.NewSingleHostReverseProxy(proxyURL)
proxy.Transport = &http.Transport{
DialContext: DialContext,
DialContext: dialVsockContext,
}
proxy.Director = func(req *http.Request) {
req.Header.Add("X-Forwarded-Host", req.Host)
Expand Down Expand Up @@ -240,3 +244,25 @@ func syncCopy(wg *sync.WaitGroup, dst io.WriteCloser, src io.ReadCloser) {
defer wg.Done()
_, _ = io.Copy(dst, src)
}

func Listen(ctx context.Context, address string) (net.Listener, error) {
if enclaveMocking {
l, err := net.Listen("tcp", address)
if err != nil {
return nil, fmt.Errorf("failed to listen to tcp address %v - %w", address, err)
}
return l, nil
}

// TODO: share with parseVsockAddr
port, err := strconv.ParseUint(strings.Split(address, ":")[1], 10, 32)
if err != nil {
return nil, fmt.Errorf("failed to parse vsock address - %w", err)
}

l, err := vsock.Listen(uint32(port), &vsock.Config{})
if err != nil {
return nil, fmt.Errorf("failed to listen to vsock %v - %w", address, err)
}
return l, nil
}
4 changes: 2 additions & 2 deletions nitro-shim/scripts/start-proxies.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ echo "cid is ${CID}"
# it's now time to set up proxy tools
if [ "${service}" = "/payments" ]; then
# setup inbound traffic proxy
export IN_ADDRS=":8080"
export OUT_ADDRS="${CID}:8080"
export IN_ADDRS=":8080,:8443"
export OUT_ADDRS="${CID}:8080,${CID}:8443"
echo "${IN_ADDRS} to ${OUT_ADDRS}"
# next startup the proxy
/enclave/viproxy > /tmp/viproxy.log &
Expand Down
5 changes: 5 additions & 0 deletions payment-test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Local files with development scretes.
/secrets/

# Home in the container
/container-home/
Loading

0 comments on commit addde59

Please sign in to comment.