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

Spike/remote verification #42

Merged
merged 22 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
aef03b0
feat: add image signing
revoltez Aug 20, 2024
eeaf7bf
feat: add verification of images
revoltez Aug 20, 2024
318bdd5
chore: decouple & re-organize scripts
revoltez Aug 20, 2024
b1960a3
feat: add policy controller chart & image verification option
revoltez Aug 21, 2024
9187968
feat: save image signatures in deployments & pod, skip uploading to r…
revoltez Aug 21, 2024
e818fb5
feat: add verification of images for localy saved signatures
revoltez Aug 21, 2024
94dd5df
feat!: create & enforce image policy if specified
revoltez Aug 22, 2024
9f9d872
fix: add policy schemas, add attestation test, fix policies targets
revoltez Aug 22, 2024
e90dc47
feat!: add application verification via its endpoint
revoltez Aug 27, 2024
1ed2571
chore: deploy tpod proxy policy with the chart
revoltez Aug 28, 2024
9f1ba4e
feat: add default verification host path
revoltez Aug 28, 2024
611e3f9
chore: properly marshal annotation values
revoltez Aug 28, 2024
1c3f3c5
chore: regroup verification settings
revoltez Aug 28, 2024
c613aa6
feat: add url verification support for verify command
revoltez Aug 28, 2024
faf0f59
chore: move tpodinfo before the TLD
revoltez Aug 29, 2024
cc1ecbe
chore: rename to verificationHost
revoltez Aug 29, 2024
7d5848c
fix: round off a few minor issues
bojidar-bg Oct 7, 2024
c18fc28
fix: return VerificationHost from the server instead of computing it …
bojidar-bg Oct 7, 2024
a0a3f89
chore!: refactor constants.go, moving things around for consistency
bojidar-bg Oct 7, 2024
a49db43
feat: cleanup old sigstore ClusterImagePolicy-ies when removing pod
bojidar-bg Oct 7, 2024
cd739e5
chore!: run buf format, fix capitalization of proto fields
bojidar-bg Oct 7, 2024
2c719ea
fixup: oops, fix potential crash
bojidar-bg Oct 7, 2024
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
13 changes: 12 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## common: ##

FROM docker.io/library/golang@sha256:52362e252f452df17c24131b021bf2ebf1c9869f65c28f88ddb326191defea9c as build-common
FROM docker.io/library/golang:1.22.6@sha256:367bb5295d3103981a86a572651d8297d6973f2ec8b62f716b007860e22cbc25 as build-common
# 1.21-bookworm

ENV DEBIAN_FRONTEND=noninteractive
Expand Down Expand Up @@ -60,4 +60,15 @@ COPY --from=build-autoscaler /usr/local/bin/autoscaler /usr/local/bin/autoscaler

ENTRYPOINT ["autoscaler"]

## tpod-proxy: ##

FROM build-common as build-tpod-proxy

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

FROM run-common as tpod-proxy

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

ENTRYPOINT ["tpod-proxy"]
4 changes: 3 additions & 1 deletion cmd/tpodserver/listen.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
var ipfsApi string
var serveAddress string
var localOciRegistry string
var proxyImage string

var listenCmd = &cobra.Command{
Use: "listen",
Expand Down Expand Up @@ -67,7 +68,7 @@ var listenCmd = &cobra.Command{
}

mux := http.NewServeMux()
mux.Handle(provider.NewTPodServerHandler(ipfsApi, ipfs, dryRun, ctrdClient, k8cl, localOciRegistry, validator, "loki.loki.svc.cluster.local:3100"))
mux.Handle(provider.NewTPodServerHandler(ipfsApi, ipfs, dryRun, ctrdClient, k8cl, localOciRegistry, validator, "loki.loki.svc.cluster.local:3100", proxyImage))
server := &http.Server{Handler: mux}

go server.Serve(listener)
Expand All @@ -91,4 +92,5 @@ func init() {
listenCmd.Flags().StringVar(&localOciRegistry, "oci-registry", "", "OCI registry used to resolve IPDR images")
listenCmd.Flags().StringVar(&ethereumRpc, "ethereum-rpc", "http://127.0.0.1:8545", "client public address")
listenCmd.Flags().StringVar(&providerKey, "ethereum-key", "", "provider account string (private key | http[s]://clef#account | /keystore#account | account (in default keystore))")
listenCmd.Flags().StringVar(&proxyImage, "proxy-image", "", "tpod proxy image url (with digest instead of tags)")
}
4 changes: 2 additions & 2 deletions cmd/tpodserver/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ var applyManifestCmd = &cobra.Command{
}

response := &pb.ProvisionPodResponse{}
namespace := tpk8s.NewTrustedPodsNamespace("tpods-xx", nil)
namespace := tpk8s.NewTrustedPodsNamespace("tpods-xx", pod, nil)
err = tpk8s.RunInNamespaceOrRevert(cmd.Context(), cl, namespace, dryRun, func(cl client.Client) error {
return tpk8s.ApplyPodRequest(cmd.Context(), cl, namespace.ObjectMeta.Name, false, pod, nil, images, secrets, response)
return tpk8s.ApplyPodRequest(cmd.Context(), cl, namespace.ObjectMeta.Name, false, pod, nil, images, secrets, response, "")
})
if err != nil {
return err
Expand Down
32 changes: 31 additions & 1 deletion cmd/trustedpods/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ import (
"github.com/spf13/cobra"
)

func checkCertificateFlags() error {
if certificateIdentity == "" || certificateOidcIssuer == "" {
return fmt.Errorf("Must specify certificate identity & oidc issuer if you signed pod images (you might consider removing --sign-images flag)")
}
return nil
}

// if no provider is selected, Fetches providers based on registry args
func fetchAndFilterProviders(ipfs *rpc.HttpApi, ethClient *ethclient.Client) (publisher.ProviderHostInfoList, error) {
registryContract := common.HexToAddress(registryContractAddress)
Expand Down Expand Up @@ -169,7 +176,7 @@ var deployPodCmd = &cobra.Command{
} else {
provisionPodclient, err = publisher.ConnectToProvider(ipfsp2p, deployment, interceptor)
if err != nil {
return err
return fmt.Errorf("Failed connecting to provider: %v", err)
}
}

Expand All @@ -192,6 +199,28 @@ var deployPodCmd = &cobra.Command{
}
}

if signImages {
err := checkCertificateFlags()
if err != nil {
return err
}
signOptions := publisher.DefaultSignOptions()
if !uploadSignatures {
signOptions.Upload = false
}
err = publisher.SignPodImages(pod, deployment, signOptions, certificateIdentity, certificateOidcIssuer)
if err != nil {
return fmt.Errorf("failed Signing images: %v", err)
}
}

if verify {
err = publisher.VerifyPodImages(pod, publisher.DefaultVerifyOptions())
if err != nil {
return fmt.Errorf("Failed verifying Pod Images: %v", err)
}
}

err = publisher.SaveDeployment(deploymentFile, deploymentFormat, deployment) // Checkpoint uploads and keys so far
if err != nil {
fmt.Fprintf(cmd.ErrOrStderr(), "warning: %v\n", err)
Expand Down Expand Up @@ -280,6 +309,7 @@ func init() {
deployPodCmd.Flags().AddFlagSet(fundFlags)
deployPodCmd.Flags().AddFlagSet(syncFlags)
deployPodCmd.Flags().AddFlagSet(registryFlags)
deployPodCmd.Flags().AddFlagSet(imageCertificateFlags)
deletePodCmd.Flags().AddFlagSet(deploymentFlags)
deletePodCmd.Flags().AddFlagSet(syncFlags)

Expand Down
25 changes: 25 additions & 0 deletions cmd/trustedpods/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,18 @@ var uploadFlags = &pflag.FlagSet{}
var ipfsApi string
var uploadImages bool
var uploadSecrets bool
var uploadSignatures bool

var signImages bool
var verify bool

var verifyFlags = &pflag.FlagSet{}
var signaturePath string
var hostHeader string

var imageCertificateFlags = &pflag.FlagSet{}
var certificateIdentity string
var certificateOidcIssuer string

var fundFlags = &pflag.FlagSet{}
var ethereumRpc string
Expand Down Expand Up @@ -61,10 +73,23 @@ var _ = func() error {
deploymentFlags.Int64Var(&expirationOffset, "token-expiration", 10, "authentication token expires after token-expiration seconds (expired after 10 seconds by default)")
deploymentFlags.StringVar(&ipfsApi, "ipfs", "/ip4/127.0.0.1/tcp/5001", "multiaddr where the ipfs/kubo api can be accessed")
deploymentFlags.BoolVar(&authorize, "authorize", false, "Create a key pair for the application and authorize the returned addresses to control the payment channel")
deploymentFlags.BoolVar(&verify, "verify", false, "verify the pod images (requires certificate-identity & certificate-oidc-issuer flags)")
deploymentFlags.BoolVar(&uploadSignatures, "upload-signatures", false, "skip uploading signatures to the registry")
deploymentFlags.BoolVar(&signImages, "sign-images", false, "sign pod images")

imageCertificateFlags.StringVar(&certificateIdentity, "certificate-identity", "", "identity used for signing the image")
imageCertificateFlags.StringVar(&certificateOidcIssuer, "certificate-oidc-issuer", "", "issuer of the oidc")

uploadFlags.StringVar(&ipfsApi, "ipfs", "/ip4/127.0.0.1/tcp/5001", "multiaddr where the ipfs/kubo api can be accessed")
uploadFlags.BoolVar(&uploadImages, "upload-images", true, "upload images")
uploadFlags.BoolVar(&uploadSecrets, "upload-secrets", true, "upload secrets")
uploadFlags.BoolVar(&signImages, "sign-images", false, "sign pod images (requires certificate identity & issuer flags)")
uploadFlags.AddFlagSet(imageCertificateFlags)
uploadFlags.BoolVar(&uploadSignatures, "upload-signatures", false, "skip uploading signatures to the registry")

verifyFlags.AddFlagSet(imageCertificateFlags)
verifyFlags.StringVar(&signaturePath, "signature", "", "path to the signature you want to verify")
verifyFlags.StringVar(&hostHeader, "host-header", "", "the verification host header when passing a tpod ip endpoint to verify")

fundFlags.StringVar(&ethereumRpc, "ethereum-rpc", "http://127.0.0.1:8545", "ethereum rpc node")
fundFlags.StringVar(&publisherKey, "ethereum-key", "", "account string (private key | http[s]://clef#account | /keystore#account | account (in default keystore))")
Expand Down
2 changes: 1 addition & 1 deletion cmd/trustedpods/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ var initPodCmd = &cobra.Command{
Containers: []*pb.Container{
{
Name: containerName,
Image: &pb.Container_Image{
Image: &pb.Image{
Url: initImageName,
},
Ports: []*pb.Container_Port{
Expand Down
16 changes: 16 additions & 0 deletions cmd/trustedpods/upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ var uploadPodCmd = &cobra.Command{
return fmt.Errorf("Failed connecting to IPFS: %w", err)
}

if signImages {
err := checkCertificateFlags()
if err != nil {
return err
}
signOptions := publisher.DefaultSignOptions()
if !uploadSignatures {
signOptions.Upload = false
}
err = publisher.SignPodImages(pod, deployment, signOptions, certificateIdentity, certificateOidcIssuer)
if err != nil {
return fmt.Errorf("failed Signing images: %v", err)
}
}

ctrdClient, err := ipcr.GetContainerdClient("k8s.io")
if err != nil {
return err
Expand Down Expand Up @@ -58,4 +73,5 @@ func init() {

uploadPodCmd.Flags().AddFlagSet(deploymentFlags)
uploadPodCmd.Flags().AddFlagSet(uploadFlags)
uploadPodCmd.Flags().AddFlagSet(imageCertificateFlags)
}
111 changes: 111 additions & 0 deletions cmd/trustedpods/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package main

import (
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"strings"

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

var verifyPodCmd = &cobra.Command{
Use: fmt.Sprintf("verify [%s]", publisher.DefaultPodFile),
Short: "Verify Pod Images",
Long: "Verify the signatures & the certificates of the specified pod images",
GroupID: "lowlevel",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
_, _, pod, deployment, err := publisher.ReadPodAndDeployment(args, manifestFormat, deploymentFormat)
if err != nil {
return err
}
// in case user deployed the pod himself and wants to verify it and the
// pod does not already specify a certificate identity & owner
pod = publisher.LinkUploadsFromDeployment(pod, deployment)
err = publisher.VerifyPodImages(pod, publisher.DefaultVerifyOptions())
if err != nil {
return fmt.Errorf("Failed verifying Pod Images: %v", err)
}
return nil
},
}

var verifyImageCmd = &cobra.Command{
Use: fmt.Sprintf("verify image"),
Short: "Verify image signature",
Long: "Verify the signatures & the certificates of the specified image name or Tpod URL",
Example: "verify ttl.sh/hello-world@sha256:d37ada95d47ad12224c205a938129df7a3e52345828b4fa27b03a98825d1e2e7 [email protected] --certificate-oidc-issuer=https://github.com/login/oauth",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
parsedURL, err := url.ParseRequestURI(args[0])
if err == nil && parsedURL.Scheme != "" && parsedURL.Host != "" {
req, err := http.NewRequest("GET", args[0], nil)
if err != nil {
log.Fatalf("Failed to create request: %v", err)
}
host := parsedURL.Host
// Check if the Host is an ip Address by detecting if the port is passed
if strings.Contains(host, ":") {
if hostHeader == "" {
return fmt.Errorf("Must pass the host-header flag when passing an ip endpoint")
}
req.Host = hostHeader
}

// Send the request
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return fmt.Errorf("failed to send request: %v", err)
}

defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("Failed to read response body: %v", err)
}

var annotationValues []tpk8s.AnnotationValue
if err := json.Unmarshal(body, &annotationValues); 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 {
image := &proto.Image{Url: av.URL, VerificationDetails: &proto.VerificationDetails{Signature: av.Signature, Identity: av.Identity, Issuer: av.Issuer}}
images = append(images, image)
}
err = publisher.VerifyImages(images, verifyOptions)
if err != nil {
return fmt.Errorf("Failed verifying Images: %v", err)
}
} else {
verifyOptions := publisher.DefaultVerifyOptions()
if signaturePath != "" {
verifyOptions.PayloadRef = signaturePath
}
image := &proto.Image{Url: args[0], VerificationDetails: &proto.VerificationDetails{Identity: certificateIdentity, Issuer: certificateOidcIssuer}}
err := publisher.VerifyImages([]*proto.Image{image}, verifyOptions)
if err != nil {
return fmt.Errorf("Failed verifying Image: %v", err)
}
}
return nil
},
}

func init() {
verifyPodCmd.Flags().AddFlagSet(verifyFlags)
verifyImageCmd.Flags().AddFlagSet(verifyFlags)
podCmd.AddCommand(verifyPodCmd)
rootCmd.AddCommand(verifyImageCmd)
}
Loading
Loading