Skip to content

Commit

Permalink
Merge pull request #54 from comrade-coop/xx-attestation-facade
Browse files Browse the repository at this point in the history
Rework the attestation facade
  • Loading branch information
bojidar-bg authored Oct 24, 2024
2 parents 4bbf371 + 0e0f40f commit c144994
Show file tree
Hide file tree
Showing 19 changed files with 423 additions and 397 deletions.
20 changes: 10 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -74,21 +74,21 @@ COPY --from=build-autoscaler /usr/local/bin/autoscaler /usr/local/bin/autoscaler

ENTRYPOINT ["autoscaler"]

## tpod-proxy: ##
## tpodproxy: ##

FROM build-common as build-tpod-proxy
FROM build-common as build-tpodproxy

COPY pkg/proxy/ ./proxy
RUN --mount=type=cache,target=/root/.cache/go-build go build -v -o /usr/local/bin/tpod-proxy ./proxy
COPY cmd/tpodproxy/ ./cmd/tpodproxy
RUN --mount=type=cache,target=/root/.cache/go-build go build -v -o /usr/local/bin/tpodproxy ./cmd/tpodproxy

FROM run-common as tpod-proxy
FROM run-common as tpodproxy

COPY --from=build-tpod-proxy /usr/local/bin/tpod-proxy /usr/local/bin/tpod-proxy
COPY --from=build-tpodproxy /usr/local/bin/tpodproxy /usr/local/bin/tpodproxy

ENTRYPOINT ["tpod-proxy"]
ENTRYPOINT ["tpodproxy"]

FROM run-common AS tpod-proxy-copy-local
FROM run-common AS tpodproxy-copy-local

COPY ./bin/proxy /usr/local/bin/tpod-proxy
COPY ./bin/tpodproxy /usr/local/bin/tpodproxy

ENTRYPOINT ["tpod-proxy"]
ENTRYPOINT ["tpodproxy"]
160 changes: 160 additions & 0 deletions cmd/tpodproxy/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package main

import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"net/url"
"os"
"os/signal"
"regexp"

tpk8s "github.com/comrade-coop/apocryph/pkg/kubernetes"
"github.com/spf13/cobra"
)

const SpecialHeaderName = "X-Apocryph-Expected"

// const SpecialCookieName = "X-Apocryph-Expected" // Could support it as a cookie too

const VerificationServiceURL = "http://verification-service.kube-system.svc.cluster.local:8080"

var ValidServiceNames = regexp.MustCompile("^tpod-vh-[a-z0-9-]{52}$") //^[a-z][a-z0-9-]{0,62}$

var currentBackingService string // e.g. tpod-XXX
var backingServiceSuffix string // e.g. .NS.svc.cluster.local
var extraAttestationDetails []tpk8s.AttestationValue // TODO: wire up
var serveAddress = ":9999"

func main() {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt)

go func() {
<-interruptChan
cancel()
}()

if err := proxyCmd.ExecuteContext(ctx); err != nil {
os.Exit(1)
}
}

var proxyCmd = &cobra.Command{
Use: "tpodproxy [backing service] [backing service suffix] [extra attestation info]",
Short: "Start a facade routing requests to the right version of an application and serving attestation information",
Long: "Start a facade routing requests to the right version of an application and serving attestation information.\n" +
"Example: tpodproxy svc1 .namespace.cluster.local:80 docker.io/org/image@sha256:1234...",
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
currentBackingService = args[0]
backingServiceSuffix = args[1]
extraAttestationDetailsJson := args[2]
err := json.Unmarshal([]byte(extraAttestationDetailsJson), &extraAttestationDetails)
if err != nil {
return err
}
if !ValidServiceNames.MatchString(currentBackingService) {
return fmt.Errorf("Invalid value (%s) for backing service, must be a valid service name", currentBackingService)
}

mux := http.NewServeMux()
mux.HandleFunc("GET /.well-known/network.apocryph.attest", attestHandler)
mux.HandleFunc("/", wildcardHandler)
s := &http.Server{
Addr: serveAddress,
Handler: mux,
}
go func() {
<-cmd.Context().Done()
err := s.Shutdown(context.TODO())
cmd.PrintErr(err)
}()

cmd.PrintErr("Server is listening on ", serveAddress)
err = s.ListenAndServe()
cmd.PrintErr(err)

return err
},
}

func init() {
proxyCmd.Flags().StringVar(&serveAddress, "address", "", "port to serve on")
}

type attestation struct { // From constellation/verify/server/server.go
Data []byte `json:"data"`
}
type AttestationResult struct {
AttestationData []byte `json:"attestation"`
AttestationError string `json:"error"`
ExtraData []tpk8s.AttestationValue `json:"extra"`
Header string `json:"header"`
}

func attestHandler(w http.ResponseWriter, r *http.Request) {
result := AttestationResult{}

// TODO: Have the extra attestation data be the user data parameter for validation
attestationJson, attestationErr := http.Get(VerificationServiceURL + "/?nonce=" + url.QueryEscape(r.URL.Query().Get("nonce")))
if attestationErr != nil {
result.AttestationError = attestationErr.Error()
} else {
err := json.NewDecoder(attestationJson.Body).Decode(&result.AttestationData)
if err != nil {
w.WriteHeader(http.StatusBadGateway)
_, _ = w.Write([]byte(err.Error()))
return
}
}

result.ExtraData = extraAttestationDetails
result.Header = fmt.Sprintf("%s: %s", SpecialHeaderName, currentBackingService)

// Write the info string to the response
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_ = json.NewEncoder(w).Encode(result)
}

func wildcardHandler(w http.ResponseWriter, r *http.Request) {
expectedService := r.Header.Get(SpecialHeaderName)
if expectedService == "" {
expectedService = currentBackingService
}
if !ValidServiceNames.MatchString(expectedService) {
w.WriteHeader(http.StatusBadRequest)
_, _ = w.Write([]byte("Invalid value for " + SpecialHeaderName))
return
}
proxyRequest := r.Clone(r.Context())
proxyRequest.URL.Scheme = "http"
proxyRequest.URL.Host = expectedService + backingServiceSuffix
proxyRequest.RequestURI = ""
// TODO: Figure out if we are doing anything about proxyRequest.Host / proxyRequest.Header["Host"]
proxyResponse, err := http.DefaultClient.Do(proxyRequest)
if err != nil {
_, _ = w.Write([]byte(err.Error()))
return
}
if r.Method == http.MethodOptions {
proxyResponse.Header.Add("Access-Control-Request-Headers", SpecialHeaderName)
}
for header, values := range proxyResponse.Header { // HACK: ...there's got to be a simpler way...
for _, value := range values {
w.Header().Add(header, value)
}
}
w.WriteHeader(proxyResponse.StatusCode)
io.Copy(w, proxyResponse.Body)
for header, values := range proxyResponse.Trailer {
for _, value := range values {
w.Header().Add(header, value)
}
}
}
13 changes: 10 additions & 3 deletions cmd/trustedpods/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ var verifyPodCmd = &cobra.Command{
},
}

type AttestationResult struct { // HACK: From ../tpodproxy/main.go
AttestationData []byte `json:"attestation"`
AttestationError string `json:"error"`
ExtraData []tpk8s.AttestationValue `json:"extra"`
Header string `json:"header"`
}

var verifyImageCmd = &cobra.Command{
Use: fmt.Sprintf("verify image"),
Short: "Verify image signature",
Expand Down Expand Up @@ -73,14 +80,14 @@ var verifyImageCmd = &cobra.Command{
return fmt.Errorf("Failed to read response body: %v", err)
}

var annotationValues []tpk8s.AnnotationValue
if err := json.Unmarshal(body, &annotationValues); err != nil {
var attestationResult AttestationResult
if err := json.Unmarshal(body, &attestationResult); err != nil {
return fmt.Errorf("Failed to unmarshal JSON response: %v", err)
}
// Verify each image from the response
verifyOptions := publisher.DefaultVerifyOptions()
images := []*proto.Image{}
for _, av := range annotationValues {
for _, av := range attestationResult.ExtraData {
image := &proto.Image{Url: av.URL, VerificationDetails: &proto.VerificationDetails{Signature: av.Signature, Identity: av.Identity, Issuer: av.Issuer}}
images = append(images, image)
}
Expand Down
31 changes: 22 additions & 9 deletions deploy/Tiltfile
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ def apocryph_resource(
kubectl_command = "kubectl get all -o yaml -n $(%s)" % namespace_cmd
apply_cmd = "set -ex; " + deploy_cmd + " 1>&2" + " && " + kubectl_command

delete_cmd = (
undeploy_cmd = (
manifest_cmd
+ " | "
+ cmdline_in_builder(
Expand All @@ -178,6 +178,8 @@ def apocryph_resource(
interactive=True,
)
)
kubectl_wait_command = "kubectl wait --for=delete ns $(%s)" % namespace_cmd
delete_cmd = "set -ex; " + undeploy_cmd + " 1>&2" + " && " + kubectl_wait_command

k8s_custom_deploy(
name,
Expand Down Expand Up @@ -332,7 +334,7 @@ def apocryph_build_with_builder(

local_resource_in_builder(
"tpodserver-go-compile",
'go build -v -buildvcs=false -ldflags="-s -w" -o bin/ ./cmd/tpodserver ./cmd/ipfs-p2p-helper ./cmd/trustedpods ./pkg/proxy',
'go build -v -buildvcs=false -ldflags="-s -w" -o bin/ ./cmd/tpodserver ./cmd/ipfs-p2p-helper ./cmd/trustedpods ./cmd/tpodproxy',
"apocryph-go-builder",
deps=[root_dir + "/cmd", root_dir + "/pkg"],
allow_parallel=True,
Expand Down Expand Up @@ -361,29 +363,38 @@ def apocryph_build_with_builder(
sync(root_dir + "/bin", "/usr/local/bin/"),
],
)

docker_build(
"comradecoop/apocryph/tpodproxy",
root_dir,
dockerfile="./Dockerfile",
target="tpodproxy-copy-local",
entrypoint=["/usr/local/bin/tpodproxy"],
only=[root_dir + "/bin"],
)
"""
# https://stackoverflow.com/a/33511811 for $(docker inspect --format ...)
docker_build_with_restart(
"comradecoop/apocryph/tpod-proxy-unsigned",
"comradecoop/apocryph/tpodproxy-unsigned",
root_dir,
dockerfile="./Dockerfile",
target="tpod-proxy-copy-local",
entrypoint=["/usr/local/bin/tpod-proxy"],
target="tpodproxy-copy-local",
entrypoint=["/usr/local/bin/tpodproxy"],
only=[root_dir + "/bin"],
live_update=[
sync(root_dir + "/bin", "/usr/local/bin/"),
],
)
cosign_sign_image_key(
"comradecoop/apocryph/tpod-proxy",
"comradecoop/apocryph/tpod-proxy-unsigned",
"comradecoop/apocryph/tpodproxy",
"comradecoop/apocryph/tpodproxy-unsigned",
cosign_key=cosign_key,
cosign_key_path=cosign_key_path,
live_update=[
sync(root_dir + "/bin", "/usr/local/bin/"),
],
)
"""


""" # TODO: Need to also build a trustedpods image for use with apocryph_resource...
Expand Down Expand Up @@ -513,8 +524,10 @@ def deploy_apocryph_stack(
resource_deps=["anvil", "ipfs", "loki", "anvil-deploy-contracts", "policy-controller"],
labels=["apocryph"],
image_keys=["image", "policy.image"],
image_deps=["comradecoop/apocryph/server", "comradecoop/apocryph/tpod-proxy"],
image_deps=["comradecoop/apocryph/server", "comradecoop/apocryph/tpodproxy"],
flags=[
"--set-json",
"policy.enable=false",
"--set-json",
"policy.issuer=false",
"--set-json",
Expand Down
2 changes: 1 addition & 1 deletion pkg/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const PAYMENT_ADDR_KEY = "PAYMENT_ADDRESS"
const PUBLISHER_ADDR_KEY = "PUBLISHER_ADDRESS"
const PROVIDER_ADDR_KEY = "PROVIDER_ADDRESS"
const POD_ID_KEY = "POD_ID"
const NamespaceKey = "NAMESPACE_NAME"

const OUTPUT_SIGNATURE_PATH = "~/.apocryph/signatures/"

const ReservedTpodProxyPort int32 = 63917
1 change: 0 additions & 1 deletion pkg/kubernetes/namespaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ const (
LabelIpfsP2P string = "coop.comrade/apocryph-p2p-helper"
AnnotationsIpfsP2P string = "coop.comrade/apocryph-p2p-helper"
SigstorePolicy string = "policy.sigstore.dev/include"
AnnotationVerificationInfo string = "coop.comrade/apocryph-verification-info"
LabelClusterImagePolicy string = "coop.comrade/apocryph-for-pod"
)

Expand Down
Loading

0 comments on commit c144994

Please sign in to comment.