From 431350b010bbbcca6f7b7f844377df7ccd8807c5 Mon Sep 17 00:00:00 2001 From: Jiankun Lu Date: Mon, 31 Jul 2023 20:01:48 -0700 Subject: [PATCH] [launcher] Add TEE server IPC implementation Start a server together with the container to allow host to container communication. Add an server in launcher to allow the workload to request custom audience tokens. --- go.work.sum | 168 ------------------- launcher/agent/agent.go | 13 +- launcher/agent/agent_test.go | 2 +- launcher/container_runner.go | 40 ++--- launcher/container_runner_test.go | 82 ++------- launcher/internal/experiments/experiments.go | 1 + launcher/teeserver/tee_server.go | 148 ++++++++++++++++ launcher/teeserver/tee_server_test.go | 53 ++++++ launcher/util.go | 30 ++++ launcher/util_test.go | 81 +++++++++ launcher/verifier/client.go | 2 + launcher/verifier/rest/rest.go | 4 + launcher/verifier/rest/rest_network_test.go | 4 +- 13 files changed, 363 insertions(+), 265 deletions(-) create mode 100644 launcher/teeserver/tee_server.go create mode 100644 launcher/teeserver/tee_server_test.go create mode 100644 launcher/util.go create mode 100644 launcher/util_test.go diff --git a/go.work.sum b/go.work.sum index 397ef657..195e9a2c 100644 --- a/go.work.sum +++ b/go.work.sum @@ -72,180 +72,12 @@ cloud.google.com/go/language v1.9.0/go.mod h1:Ns15WooPM5Ad/5no/0n81yUetis74g3zrb cloud.google.com/go/lifesciences v0.8.0/go.mod h1:lFxiEOMqII6XggGbOnKiyZ7IBwoIqA84ClvoezaA/bo= cloud.google.com/go/logging v1.4.2 h1:Mu2Q75VBDQlW1HlBMjTX4X84UFR73G1TiLlRYc/b7tA= cloud.google.com/go/logging v1.4.2/go.mod h1:jco9QZSx8HiVVqLJReq7z7bVdj0P1Jb9PDFs63T+axo= -cloud.google.com/go/logging v1.7.0 h1:CJYxlNNNNAMkHp9em/YEXcfJg+rPDg7YfwoRpMU+t5I= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/managedidentities v1.5.0/go.mod h1:+dWcZ0JlUmpuxpIDfyP5pP5y0bLdRwOS4Lp7gMni/LA= -cloud.google.com/go/maps v0.7.0/go.mod h1:3GnvVl3cqeSvgMcpRlQidXsPYuDGQ8naBis7MVzpXsY= -cloud.google.com/go/mediatranslation v0.7.0/go.mod h1:LCnB/gZr90ONOIQLgSXagp8XUW1ODs2UmUMvcgMfI2I= -cloud.google.com/go/memcache v1.9.0/go.mod h1:8oEyzXCu+zo9RzlEaEjHl4KkgjlNDaXbCQeQWlzNFJM= -cloud.google.com/go/metastore v1.10.0/go.mod h1:fPEnH3g4JJAk+gMRnrAnoqyv2lpUCqJPWOodSaf45Eo= -cloud.google.com/go/monitoring v1.13.0/go.mod h1:k2yMBAB1H9JT/QETjNkgdCGD9bPF712XiLTVr+cBrpw= -cloud.google.com/go/networkconnectivity v1.11.0/go.mod h1:iWmDD4QF16VCDLXUqvyspJjIEtBR/4zq5hwnY2X3scM= -cloud.google.com/go/networkmanagement v1.6.0/go.mod h1:5pKPqyXjB/sgtvB5xqOemumoQNB7y95Q7S+4rjSOPYY= -cloud.google.com/go/networksecurity v0.8.0/go.mod h1:B78DkqsxFG5zRSVuwYFRZ9Xz8IcQ5iECsNrPn74hKHU= -cloud.google.com/go/notebooks v1.8.0/go.mod h1:Lq6dYKOYOWUCTvw5t2q1gp1lAp0zxAxRycayS0iJcqQ= -cloud.google.com/go/optimization v1.3.1/go.mod h1:IvUSefKiwd1a5p0RgHDbWCIbDFgKuEdB+fPPuP0IDLI= -cloud.google.com/go/orchestration v1.6.0/go.mod h1:M62Bevp7pkxStDfFfTuCOaXgaaqRAga1yKyoMtEoWPQ= -cloud.google.com/go/orgpolicy v1.10.0/go.mod h1:w1fo8b7rRqlXlIJbVhOMPrwVljyuW5mqssvBtU18ONc= -cloud.google.com/go/osconfig v1.11.0/go.mod h1:aDICxrur2ogRd9zY5ytBLV89KEgT2MKB2L/n6x1ooPw= -cloud.google.com/go/oslogin v1.9.0/go.mod h1:HNavntnH8nzrn8JCTT5fj18FuJLFJc4NaZJtBnQtKFs= -cloud.google.com/go/phishingprotection v0.7.0/go.mod h1:8qJI4QKHoda/sb/7/YmMQ2omRLSLYSu9bU0EKCNI+Lk= -cloud.google.com/go/policytroubleshooter v1.6.0/go.mod h1:zYqaPTsmfvpjm5ULxAyD/lINQxJ0DDsnWOP/GZ7xzBc= -cloud.google.com/go/privatecatalog v0.8.0/go.mod h1:nQ6pfaegeDAq/Q5lrfCQzQLhubPiZhSaNhIgfJlnIXs= -cloud.google.com/go/pubsub v1.30.0/go.mod h1:qWi1OPS0B+b5L+Sg6Gmc9zD1Y+HaM0MdUr7LsupY1P4= -cloud.google.com/go/pubsublite v1.7.0/go.mod h1:8hVMwRXfDfvGm3fahVbtDbiLePT3gpoiJYJY+vxWxVM= -cloud.google.com/go/recaptchaenterprise/v2 v2.7.0/go.mod h1:19wVj/fs5RtYtynAPJdDTb69oW0vNHYDBTbB4NvMD9c= -cloud.google.com/go/recommendationengine v0.7.0/go.mod h1:1reUcE3GIu6MeBz/h5xZJqNLuuVjNg1lmWMPyjatzac= -cloud.google.com/go/recommender v1.9.0/go.mod h1:PnSsnZY7q+VL1uax2JWkt/UegHssxjUVVCrX52CuEmQ= -cloud.google.com/go/redis v1.11.0/go.mod h1:/X6eicana+BWcUda5PpwZC48o37SiFVTFSs0fWAJ7uQ= -cloud.google.com/go/resourcemanager v1.7.0/go.mod h1:HlD3m6+bwhzj9XCouqmeiGuni95NTrExfhoSrkC/3EI= -cloud.google.com/go/resourcesettings v1.5.0/go.mod h1:+xJF7QSG6undsQDfsCJyqWXyBwUoJLhetkRMDRnIoXA= -cloud.google.com/go/retail v1.12.0/go.mod h1:UMkelN/0Z8XvKymXFbD4EhFJlYKRx1FGhQkVPU5kF14= -cloud.google.com/go/run v0.9.0/go.mod h1:Wwu+/vvg8Y+JUApMwEDfVfhetv30hCG4ZwDR/IXl2Qg= -cloud.google.com/go/scheduler v1.9.0/go.mod h1:yexg5t+KSmqu+njTIh3b7oYPheFtBWGcbVUYF1GGMIc= -cloud.google.com/go/secretmanager v1.10.0/go.mod h1:MfnrdvKMPNra9aZtQFvBcvRU54hbPD8/HayQdlUgJpU= -cloud.google.com/go/security v1.13.0/go.mod h1:Q1Nvxl1PAgmeW0y3HTt54JYIvUdtcpYKVfIB8AOMZ+0= -cloud.google.com/go/securitycenter v1.19.0/go.mod h1:LVLmSg8ZkkyaNy4u7HCIshAngSQ8EcIRREP3xBnyfag= -cloud.google.com/go/servicecontrol v1.11.1/go.mod h1:aSnNNlwEFBY+PWGQ2DoM0JJ/QUXqV5/ZD9DOLB7SnUk= -cloud.google.com/go/servicedirectory v1.9.0/go.mod h1:29je5JjiygNYlmsGz8k6o+OZ8vd4f//bQLtvzkPPT/s= -cloud.google.com/go/servicemanagement v1.8.0/go.mod h1:MSS2TDlIEQD/fzsSGfCdJItQveu9NXnUniTrq/L8LK4= -cloud.google.com/go/serviceusage v1.6.0/go.mod h1:R5wwQcbOWsyuOfbP9tGdAnCAc6B9DRwPG1xtWMDeuPA= -cloud.google.com/go/shell v1.6.0/go.mod h1:oHO8QACS90luWgxP3N9iZVuEiSF84zNyLytb+qE2f9A= -cloud.google.com/go/spanner v1.45.0/go.mod h1:FIws5LowYz8YAE1J8fOS7DJup8ff7xJeetWEo5REA2M= -cloud.google.com/go/speech v1.15.0/go.mod h1:y6oH7GhqCaZANH7+Oe0BhgIogsNInLlz542tg3VqeYI= -cloud.google.com/go/storage v1.22.1 h1:F6IlQJZrZM++apn9V5/VfS3gbTUYg98PS3EMQAzqtfg= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/storage v1.29.0/go.mod h1:4puEjyTKnku6gfKoTfNOU/W+a9JyuVNxjpS5GBrB8h4= -cloud.google.com/go/storagetransfer v1.8.0/go.mod h1:JpegsHHU1eXg7lMHkvf+KE5XDJ7EQu0GwNJbbVGanEw= -cloud.google.com/go/talent v1.5.0/go.mod h1:G+ODMj9bsasAEJkQSzO2uHQWXHHXUomArjWQQYkqK6c= -cloud.google.com/go/texttospeech v1.6.0/go.mod h1:YmwmFT8pj1aBblQOI3TfKmwibnsfvhIBzPXcW4EBovc= -cloud.google.com/go/tpu v1.5.0/go.mod h1:8zVo1rYDFuW2l4yZVY0R0fb/v44xLh3llq7RuV61fPM= -cloud.google.com/go/trace v1.9.0/go.mod h1:lOQqpE5IaWY0Ixg7/r2SjixMuc6lfTFeO4QGM4dQWOk= -cloud.google.com/go/translate v1.7.0/go.mod h1:lMGRudH1pu7I3n3PETiOB2507gf3HnfLV8qlkHZEyos= -cloud.google.com/go/video v1.15.0/go.mod h1:SkgaXwT+lIIAKqWAJfktHT/RbgjSuY6DobxEp0C5yTQ= -cloud.google.com/go/videointelligence v1.10.0/go.mod h1:LHZngX1liVtUhZvi2uNS0VQuOzNi2TkY1OakiuoUOjU= -cloud.google.com/go/vision/v2 v2.7.0/go.mod h1:H89VysHy21avemp6xcf9b9JvZHVehWbET0uT/bcuY/0= -cloud.google.com/go/vmmigration v1.6.0/go.mod h1:bopQ/g4z+8qXzichC7GW1w2MjbErL54rk3/C843CjfY= -cloud.google.com/go/vmwareengine v0.3.0/go.mod h1:wvoyMvNWdIzxMYSpH/R7y2h5h3WFkx6d+1TIsP39WGY= -cloud.google.com/go/vpcaccess v1.6.0/go.mod h1:wX2ILaNhe7TlVa4vC5xce1bCnqE3AeH27RV31lnmZes= -cloud.google.com/go/webrisk v1.8.0/go.mod h1:oJPDuamzHXgUc+b8SiHRcVInZQuybnvEW72PqTc7sSg= -cloud.google.com/go/websecurityscanner v1.5.0/go.mod h1:Y6xdCPy81yi0SQnDY1xdNTNpfY1oAgXUlcfN3B3eSng= -cloud.google.com/go/workflows v1.10.0/go.mod h1:fZ8LmRmZQWacon9UCX1r/g/DfAXx5VcPALq2CxzdePw= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= -github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cncf/udpa/go v0.0.0-20220112060539-c52dc94e7fbe/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20220314180256-7f1daf1720fc/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230105202645-06c439db220b/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20230607035331-e9ce68804cb4/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/envoyproxy/go-control-plane v0.10.3/go.mod h1:fJJn/j26vwOu972OllsvAgJJM//w9BV6Fxbg2LuVd34= -github.com/envoyproxy/go-control-plane v0.11.1-0.20230524094728-9239064ad72f/go.mod h1:sfYdkwUW4BA3PbKjySwjJy+O4Pu0h62rlqCMHNk+K+Q= -github.com/envoyproxy/protoc-gen-validate v0.6.7/go.mod h1:dyJXwwfPK2VSqiB9Klm1J6romD608Ba7Hij42vrOBCo= -github.com/envoyproxy/protoc-gen-validate v0.9.1/go.mod h1:OKNgG7TCp5pF4d6XftA0++PMirau2/yoOwVac3AbF2w= -github.com/envoyproxy/protoc-gen-validate v0.10.1/go.mod h1:DRjgyB0I43LtJapqN6NiRwroiAU2PaFuvk/vjgh61ss= -github.com/golang/glog v1.0.0/go.mod h1:EWib/APOK0SL3dFbYqvxE3UYd8E6s1ouQ7iEp/0LWV4= -github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ= -github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-sev-guest v0.8.0 h1:IIZIqdcMJXgTm1nMvId442OUpYebbWDWa9bi9/lUUwc= -github.com/google/go-tpm v0.3.4-0.20230613064043-511507721cb1/go.mod h1:Yj9bYgsIKoza8oMlxZqvqgUIDKFaExnuLaDdOtFCwG4= -github.com/google/go-tpm-tools v0.3.9/go.mod h1:22JvWmHcD5w55cs+nMeqDGDxgNS15/2pDq2cLqnc3rc= -github.com/google/go-tpm-tools v0.3.10/go.mod h1:HQfQboO+M8pRtBfO5U3KMhwzfC/XC3TaMCgRfTpII8Q= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian/v3 v3.3.2/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/s2a-go v0.1.3/go.mod h1:Ej+mSEMGRnqRzjc7VtF+jdBwYG5fuJfiZ8ELkjEwM0A= -github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= -github.com/googleapis/enterprise-certificate-proxy v0.1.0 h1:zO8WHNx/MYiAKJ3d5spxZXZE6KHmIQGQcAzwUzV7qQw= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.1/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= github.com/googleapis/gax-go v2.0.2+incompatible h1:silFMLAnr330+NRuag/VjIGF7TLp/LBrV2CJKFLWEww= -github.com/googleapis/gax-go/v2 v2.4.0 h1:dS9eYAjhrE2RjmzYw2XAPvcXfmcQLtFEQWn0CR82awk= -github.com/googleapis/gax-go/v2 v2.7.0/go.mod h1:TEop28CZZQ2y+c0VxMUmu1lV+fQx57QpBWsYpwqHJx8= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/gax-go/v2 v2.10.0/go.mod h1:4UOEnMCrxsSqQ940WnTiD6qJ63le2ev3xfyagutxiPw= -github.com/googleapis/go-type-adapters v1.0.0 h1:9XdMn+d/G57qq1s8dNc5IesGCXHf6V2HZ2JwRxfA2tA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.7.0/go.mod h1:hgWBS7lorOAVIJEQMi4ZsPv9hVvWI6+ch50m39Pf2Ks= -github.com/josephlr/google-api-go-client v0.86.1 h1:nSNMjyd+GV04cF99zd5r/Ql5sZvA0ehya/Qtj/GbE68= -github.com/josephlr/google-api-go-client v0.86.1/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -github.com/lyft/protoc-gen-star v0.6.0/go.mod h1:TGAoBVkt8w7MPG72TrKIu85MIdXwDuzJYeZuUPFPNwA= -github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/pborman/uuid v1.2.1 h1:+ZZIw58t/ozdjRaXh/3awHfmWRbzYxJoAdNJxe/3pvw= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= -github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M= -go.opentelemetry.io/proto/otlp v0.15.0/go.mod h1:H7XAot3MsfNsj7EXtrA2q5xSNQ10UqI405h3+duxN4U= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= -golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= -golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.9.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g= -golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2 h1:+jnHzr9VPj32ykQVai5DNahi9+NSp7yYuCsl5eAQtL0= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.4.0/go.mod h1:RznEsdpjGAINPTOF0UH/t+xJ75L18YO3Ho6Pyn+uRec= -golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= -golang.org/x/oauth2 v0.6.0/go.mod h1:ycmewcwgD4Rpr3eZJLSB4Kyyljb3qDh40vJ8STE5HKw= -golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sys v0.0.0-20220608164250-635b8c9b7f68/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= -golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.7.0/go.mod h1:4pg6aUX35JBAogB10C9AtvVL+qowtN4pT3CGSQex14s= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.106.0/go.mod h1:2Ts0XTHNVWxypznxWOYUeI4g3WdP9Pk2Qk58+a/O9MY= -google.golang.org/api v0.110.0/go.mod h1:7FC4Vvx1Mooxh8C5HWjzZHcavuS2f6pmJpZx60ca7iI= -google.golang.org/api v0.111.0/go.mod h1:qtFHvU9mhgTJegR31csQ+rwxyUTHOKFqCKWp1J0fdw0= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/api v0.122.0/go.mod h1:gcitW0lvnyWjSp9nKxAbdHKIZ6vF4aajGueeslZOyms= -google.golang.org/genproto v0.0.0-20210517163617-5e0236093d7a/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20220329172620-7be39ac1afc7/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f h1:hJ/Y5SqPXbarffmAsApliUlcvMU+wScNGfyop4bZm8o= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230209215440-0dfe4f8abfcc/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/genproto v0.0.0-20230222225845-10f96fb3dbec/go.mod h1:3Dl5ZL0q0isWJt+FVcfpQyirqemEuLAK/iFvg1UP1Hw= -google.golang.org/genproto v0.0.0-20230303212802-e74f57abe488/go.mod h1:TvhZT5f700eVlTNwND1xoEZQeWTB2RY/65kplwl/bFA= -google.golang.org/genproto v0.0.0-20230320184635-7606e756e683/go.mod h1:NWraEVixdDnqcqQ30jipen1STv2r/n24Wb7twVTGR4s= -google.golang.org/genproto v0.0.0-20230403163135-c38d8f061ccd/go.mod h1:UUQDJDOlWu4KYeJZffbWgBkS1YFobzKbLVfK69pe0Ak= -google.golang.org/genproto v0.0.0-20230525234025-438c736192d0/go.mod h1:9ExIQyXL5hZrHzQceCwuSYwZZ5QZBazOcprJ5rgs3lY= -google.golang.org/genproto/googleapis/api v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:vHYtlOoi6TsQ3Uk2yxR7NI5z8uoV+3pZtR4jmHIkRig= -google.golang.org/genproto/googleapis/bytestream v0.0.0-20230530153820-e85fd2cbaebc/go.mod h1:ylj+BE99M198VPbBh6A8d9n3w8fChvyLK3wwBOjXBFA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230525234030-28d5490b6b19/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/genproto/googleapis/rpc v0.0.0-20230526203410-71b5a4ffd15e/go.mod h1:66JfowdXAEgad5O9NnYcsNPLCPZJD++2L9X0PCMODrA= -google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8= -google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/grpc v1.55.0/go.mod h1:iYEXKGkEBhg1PjZQvoYEVPTDkHo1/bjTnfwTeGONTY8= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.29.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= diff --git a/launcher/agent/agent.go b/launcher/agent/agent.go index 0e3f157e..a521386d 100644 --- a/launcher/agent/agent.go +++ b/launcher/agent/agent.go @@ -34,7 +34,14 @@ type principalIDTokenFetcher func(audience string) ([][]byte, error) // struct to make testing easier. type AttestationAgent interface { MeasureEvent(cel.Content) error - Attest(context.Context) ([]byte, error) + Attest(context.Context, AttestAgentOpts) ([]byte, error) +} + +// AttestAgentOpts contains user generated options when calling the +// VerifyAttestation API +type AttestAgentOpts struct { + Aud string + Nonces []string } type agent struct { @@ -76,7 +83,7 @@ func (a *agent) MeasureEvent(event cel.Content) error { // Attest fetches the nonce and connection ID from the Attestation Service, // creates an attestation message, and returns the resultant // principalIDTokens and Metadata Server-generated ID tokens for the instance. -func (a *agent) Attest(ctx context.Context) ([]byte, error) { +func (a *agent) Attest(ctx context.Context, opts AttestAgentOpts) ([]byte, error) { challenge, err := a.client.CreateChallenge(ctx) if err != nil { return nil, err @@ -96,6 +103,8 @@ func (a *agent) Attest(ctx context.Context) ([]byte, error) { Challenge: challenge, GcpCredentials: principalTokens, Attestation: attestation, + CustomAudience: opts.Aud, + CustomNonce: opts.Nonces, } if a.launchSpec.Experiments.EnableSignedContainerImage { diff --git a/launcher/agent/agent_test.go b/launcher/agent/agent_test.go index 259b4142..63e81779 100644 --- a/launcher/agent/agent_test.go +++ b/launcher/agent/agent_test.go @@ -61,7 +61,7 @@ func TestAttest(t *testing.T) { agent := CreateAttestationAgent(tpm, client.AttestationKeyECC, verifierClient, tc.principalIDTokenFetcher, tc.containerSignaturesFetcher, tc.launchSpec, log.Default()) - tokenBytes, err := agent.Attest(context.Background()) + tokenBytes, err := agent.Attest(context.Background(), AttestAgentOpts{}) if err != nil { t.Errorf("failed to attest to Attestation Service: %v", err) } diff --git a/launcher/container_runner.go b/launcher/container_runner.go index 6105230e..98c1cb1e 100644 --- a/launcher/container_runner.go +++ b/launcher/container_runner.go @@ -32,13 +32,13 @@ import ( "github.com/google/go-tpm-tools/launcher/internal/signaturediscovery" "github.com/google/go-tpm-tools/launcher/launcherfile" "github.com/google/go-tpm-tools/launcher/spec" + "github.com/google/go-tpm-tools/launcher/teeserver" "github.com/google/go-tpm-tools/launcher/verifier" "github.com/google/go-tpm-tools/launcher/verifier/rest" v1 "github.com/opencontainers/image-spec/specs-go/v1" specs "github.com/opencontainers/runtime-spec/specs-go" "golang.org/x/oauth2" "golang.org/x/oauth2/google" - "google.golang.org/api/impersonate" "google.golang.org/api/option" ) @@ -53,6 +53,8 @@ type ContainerRunner struct { const tokenFileTmp = ".token.tmp" +const teeServerSocket = "teeserver.sock" + // Since we only allow one container on a VM, using a deterministic id is probably fine const ( containerID = "tee-container" @@ -74,26 +76,6 @@ const ( defaultRefreshJitter = 0.1 ) -func fetchImpersonatedToken(ctx context.Context, serviceAccount string, audience string, opts ...option.ClientOption) ([]byte, error) { - config := impersonate.IDTokenConfig{ - Audience: audience, - TargetPrincipal: serviceAccount, - IncludeEmail: true, - } - - tokenSource, err := impersonate.IDTokenSource(ctx, config, opts...) - if err != nil { - return nil, fmt.Errorf("error creating token source: %v", err) - } - - token, err := tokenSource.Token() - if err != nil { - return nil, fmt.Errorf("error retrieving token: %v", err) - } - - return []byte(token.AccessToken), nil -} - // NewRunner returns a runner. func NewRunner(ctx context.Context, cdClient *containerd.Client, token oauth2.Token, launchSpec spec.LaunchSpec, mdsClient *metadata.Client, tpm io.ReadWriteCloser, logger *log.Logger, serialConsole *os.File) (*ContainerRunner, error) { image, err := initImage(ctx, cdClient, launchSpec, token) @@ -103,6 +85,7 @@ func NewRunner(ctx context.Context, cdClient *containerd.Client, token oauth2.To mounts := make([]specs.Mount, 0) mounts = appendTokenMounts(mounts) + envs, err := formatEnvVars(launchSpec.Envs) if err != nil { return nil, err @@ -214,7 +197,7 @@ func NewRunner(ctx context.Context, cdClient *containerd.Client, token oauth2.To // Fetch impersonated ID tokens. for _, sa := range launchSpec.ImpersonateServiceAccounts { - idToken, err := fetchImpersonatedToken(ctx, sa, audience) + idToken, err := FetchImpersonatedToken(ctx, sa, audience) if err != nil { return nil, fmt.Errorf("failed to get impersonated token for %v: %w", sa, err) } @@ -360,7 +343,8 @@ func (r *ContainerRunner) measureContainerClaims(ctx context.Context) error { // The token file will be written to a tmp file and then renamed. func (r *ContainerRunner) refreshToken(ctx context.Context) (time.Duration, error) { r.logger.Print("refreshing attestation verifier OIDC token") - token, err := r.attestAgent.Attest(ctx) + // request a default token + token, err := r.attestAgent.Attest(ctx, agent.AttestAgentOpts{}) if err != nil { return 0, fmt.Errorf("failed to retrieve attestation service token: %v", err) } @@ -512,6 +496,16 @@ func (r *ContainerRunner) Run(ctx context.Context) error { } r.logger.Printf("EnableTestFeatureForImage is set to %v\n", r.launchSpec.Experiments.EnableTestFeatureForImage) + // create and start the TEE server behind the experiment + if r.launchSpec.Experiments.EnableOnDemandAttestation { + r.logger.Println("EnableOnDemandAttestation is enabled: initializing TEE server.") + teeServer, err := teeserver.New(path.Join(launcherfile.HostTmpPath, teeServerSocket), r.attestAgent, r.logger) + if err != nil { + return fmt.Errorf("failed to create the TEE server: %v", err) + } + go teeServer.Serve() + defer teeServer.Shutdown(ctx) + } var streamOpt cio.Opt switch r.launchSpec.LogRedirect { diff --git a/launcher/container_runner_test.go b/launcher/container_runner_test.go index 20bd7680..1f23fdf5 100644 --- a/launcher/container_runner_test.go +++ b/launcher/container_runner_test.go @@ -5,12 +5,9 @@ import ( "context" "crypto/rand" "crypto/rsa" - "encoding/json" "errors" "fmt" - "io" "log" - "net/http" "os" "path" "strconv" @@ -23,10 +20,10 @@ import ( "github.com/containerd/containerd/namespaces" "github.com/golang-jwt/jwt/v4" "github.com/google/go-tpm-tools/cel" + "github.com/google/go-tpm-tools/launcher/agent" "github.com/google/go-tpm-tools/launcher/launcherfile" "github.com/google/go-tpm-tools/launcher/spec" "golang.org/x/oauth2" - "google.golang.org/api/option" ) const ( @@ -36,7 +33,7 @@ const ( // Fake attestation agent. type fakeAttestationAgent struct { measureEventFunc func(cel.Content) error - attestFunc func(context.Context) ([]byte, error) + attestFunc func(context.Context, agent.AttestAgentOpts) ([]byte, error) } func (f *fakeAttestationAgent) MeasureEvent(event cel.Content) error { @@ -47,9 +44,9 @@ func (f *fakeAttestationAgent) MeasureEvent(event cel.Content) error { return fmt.Errorf("unimplemented") } -func (f *fakeAttestationAgent) Attest(ctx context.Context) ([]byte, error) { +func (f *fakeAttestationAgent) Attest(ctx context.Context, _ agent.AttestAgentOpts) ([]byte, error) { if f.attestFunc != nil { - return f.attestFunc(ctx) + return f.attestFunc(ctx, agent.AttestAgentOpts{}) } return nil, fmt.Errorf("unimplemented") @@ -102,7 +99,7 @@ func TestRefreshToken(t *testing.T) { runner := ContainerRunner{ attestAgent: &fakeAttestationAgent{ - attestFunc: func(context.Context) ([]byte, error) { + attestFunc: func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return expectedToken, nil }, }, @@ -146,7 +143,7 @@ func TestRefreshTokenError(t *testing.T) { { name: "Attest fails", agent: &fakeAttestationAgent{ - attestFunc: func(context.Context) ([]byte, error) { + attestFunc: func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return nil, errors.New("attest error") }, }, @@ -154,7 +151,7 @@ func TestRefreshTokenError(t *testing.T) { { name: "Attest returns expired token", agent: &fakeAttestationAgent{ - attestFunc: func(context.Context) ([]byte, error) { + attestFunc: func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return createJWT(t, -5*time.Second), nil }, }, @@ -184,7 +181,7 @@ func TestFetchAndWriteTokenSucceeds(t *testing.T) { runner := ContainerRunner{ attestAgent: &fakeAttestationAgent{ - attestFunc: func(context.Context) ([]byte, error) { + attestFunc: func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return expectedToken, nil }, }, @@ -212,11 +209,11 @@ func TestTokenIsNotChangedIfRefreshFails(t *testing.T) { expectedToken := createJWT(t, 5*time.Second) ttl := 5 * time.Second - successfulAttestFunc := func(context.Context) ([]byte, error) { + successfulAttestFunc := func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return expectedToken, nil } - errorAttestFunc := func(context.Context) ([]byte, error) { + errorAttestFunc := func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return nil, errors.New("attest unsuccessful") } @@ -289,7 +286,7 @@ func testRetryPolicyWithNTries(t *testing.T, numTries int, expectRefresh bool) { // Wait the initial token's 5s plus a second per retry (MaxInterval). ttl := time.Duration(numTries)*time.Second + 5*time.Second retry := -1 - attestFunc := func(context.Context) ([]byte, error) { + attestFunc := func(context.Context, agent.AttestAgentOpts) ([]byte, error) { retry++ // Success on the initial fetch (subsequent calls use refresher goroutine). if retry == 0 { @@ -350,7 +347,7 @@ func TestFetchAndWriteTokenWithTokenRefresh(t *testing.T) { runner := ContainerRunner{ attestAgent: &fakeAttestationAgent{ - attestFunc: func(context.Context) ([]byte, error) { + attestFunc: func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return expectedToken, nil }, }, @@ -374,7 +371,7 @@ func TestFetchAndWriteTokenWithTokenRefresh(t *testing.T) { // Change attest agent to return new token. expectedRefreshedToken := createJWT(t, 10*time.Second) runner.attestAgent = &fakeAttestationAgent{ - attestFunc: func(context.Context) ([]byte, error) { + attestFunc: func(context.Context, agent.AttestAgentOpts) ([]byte, error) { return expectedRefreshedToken, nil }, } @@ -402,59 +399,6 @@ func TestFetchAndWriteTokenWithTokenRefresh(t *testing.T) { } } -type testRoundTripper struct { - roundTripFunc func(*http.Request) *http.Response -} - -func (t *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { - return t.roundTripFunc(req), nil -} - -type idTokenResp struct { - Token string `json:"token"` -} - -func TestFetchImpersonatedToken(t *testing.T) { - expectedEmail := "test2@google.com" - - expectedToken := []byte("test_token") - - expectedURL := fmt.Sprintf(idTokenEndpoint, expectedEmail) - client := &http.Client{ - Transport: &testRoundTripper{ - roundTripFunc: func(req *http.Request) *http.Response { - if req.URL.String() != expectedURL { - t.Errorf("HTTP call was not made to a endpoint: got %v, want %v", req.URL.String(), expectedURL) - } - - resp := idTokenResp{ - Token: string(expectedToken), - } - - respBody, err := json.Marshal(resp) - if err != nil { - t.Fatalf("Unable to marshal HTTP response: %v", err) - } - - return &http.Response{ - StatusCode: http.StatusOK, - Header: make(http.Header), - Body: io.NopCloser(bytes.NewBuffer(respBody)), - } - }, - }, - } - - token, err := fetchImpersonatedToken(context.Background(), expectedEmail, "test_aud", option.WithHTTPClient(client)) - if err != nil { - t.Fatalf("fetchImpersonatedToken returned error: %v", err) - } - - if !bytes.Equal(token, expectedToken) { - t.Errorf("fetchImpersonatedToken did not return expected token: got %v, want %v", token, expectedToken) - } -} - func TestGetNextRefresh(t *testing.T) { // 0 <= random < 1. for _, randNum := range []float64{0, .1415926, .5, .75, .999999999} { diff --git a/launcher/internal/experiments/experiments.go b/launcher/internal/experiments/experiments.go index 1d6aec36..69d642b7 100644 --- a/launcher/internal/experiments/experiments.go +++ b/launcher/internal/experiments/experiments.go @@ -13,6 +13,7 @@ import ( type Experiments struct { EnableTestFeatureForImage bool EnableSignedContainerImage bool + EnableOnDemandAttestation bool } // New takes a filepath, opens the file, and calls ReadJsonInput with the contents diff --git a/launcher/teeserver/tee_server.go b/launcher/teeserver/tee_server.go new file mode 100644 index 00000000..65a3e77c --- /dev/null +++ b/launcher/teeserver/tee_server.go @@ -0,0 +1,148 @@ +// Package teeserver implements a server to be run in the launcher. +// Used for communicate between the host/launcher and the container. +package teeserver + +import ( + "context" + "encoding/json" + "fmt" + "log" + "net" + "net/http" + "os" + "path/filepath" + + "github.com/google/go-tpm-tools/launcher/agent" + "github.com/google/go-tpm-tools/launcher/launcherfile" +) + +type attestHandler struct { + attestAgent agent.AttestationAgent + defaultTokenFile string + logger *log.Logger +} + +type customTokenRequest struct { + Audience string `json:"audience"` + Nonces []string `json:"nonces"` +} + +// TeeServer is a server that can be called from a container through a unix +// socket file. +type TeeServer struct { + server *http.Server + netListener net.Listener +} + +// New takes in a socket and start to listen to it, and create a server +func New(unixSock string, a agent.AttestationAgent, logger *log.Logger) (*TeeServer, error) { + var err error + nl, err := net.Listen("unix", unixSock) + if err != nil { + return nil, fmt.Errorf("cannot listen to the socket [%s]: %v", unixSock, err) + } + + teeServer := TeeServer{ + netListener: nl, + server: &http.Server{ + Handler: (&attestHandler{ + attestAgent: a, + defaultTokenFile: filepath.Join(launcherfile.HostTmpPath, launcherfile.AttestationVerifierTokenFilename), + logger: logger, + }).Handler(), + }, + } + return &teeServer, nil +} + +// Handler creates a multiplexer for the server. +func (a *attestHandler) Handler() http.Handler { + mux := http.NewServeMux() + // to test default token: curl --unix-socket http://localhost/v1/token + // to test custom token: + // curl -d '{"audience":"", "nonces":[""]}' -H "Content-Type: application/json" -X POST + // --unix-socket /tmp/container_launcher/teeserver.sock http://localhost/v1/token + + mux.HandleFunc("/v1/token", a.getToken) + return mux +} + +// getDefaultToken handles the request to get the default OIDC token. +// For now this function will just read the content of the file and return. +// Later, this function can use attestation agent to get a token directly. +func (a *attestHandler) getToken(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "text/html") + + switch r.Method { + case "GET": + // this could call Attest(context.Background()) directly later. + data, err := os.ReadFile(a.defaultTokenFile) + + if err != nil { + a.logger.Print(err) + w.WriteHeader(http.StatusNotFound) + w.Write([]byte("failed to get the token")) + return + } + w.WriteHeader(http.StatusOK) + w.Write(data) + return + case "POST": + var tokenReq customTokenRequest + decoder := json.NewDecoder(r.Body) + decoder.DisallowUnknownFields() + + err := decoder.Decode(&tokenReq) + if err != nil { + a.logger.Print(err) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + if tokenReq.Audience == "" { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte("use GET request for the default identity token")) + return + } + + tok, err := a.attestAgent.Attest(context.Background(), + agent.AttestAgentOpts{ + Aud: tokenReq.Audience, + Nonces: tokenReq.Nonces, + }) + if err != nil { + a.logger.Print(err) + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + w.WriteHeader(http.StatusOK) + w.Write(tok) + return + } + + w.WriteHeader(http.StatusBadRequest) + // TODO: add an url pointing to the REST API document + w.Write([]byte("TEE server received invalid request")) +} + +// Serve starts the server, will block until the server shutdown. +func (s *TeeServer) Serve() error { + return s.server.Serve(s.netListener) +} + +// Shutdown will terminate the server and the underlying listener. +func (s *TeeServer) Shutdown(ctx context.Context) error { + err := s.server.Shutdown(ctx) + err2 := s.netListener.Close() + + if err != nil { + return err + } + if err2 != nil { + return err2 + } + return nil +} diff --git a/launcher/teeserver/tee_server_test.go b/launcher/teeserver/tee_server_test.go new file mode 100644 index 00000000..e3612252 --- /dev/null +++ b/launcher/teeserver/tee_server_test.go @@ -0,0 +1,53 @@ +package teeserver + +import ( + "io" + "log" + "net/http" + "net/http/httptest" + "os" + "path" + "testing" + + "github.com/google/go-tpm-tools/launcher/launcherfile" +) + +func TestGetDefaultToken(t *testing.T) { + tmpDir := t.TempDir() + tmpToken := path.Join(tmpDir, launcherfile.AttestationVerifierTokenFilename) + // An empty attestHandler is fine for now as it is not being used + // in the handler. + ah := attestHandler{defaultTokenFile: tmpToken, logger: log.Default()} + + req := httptest.NewRequest(http.MethodGet, "/v1/token", nil) + w := httptest.NewRecorder() + ah.getToken(w, req) + _, err := io.ReadAll(w.Result().Body) + if err != nil { + t.Error(err) + } + + // The token file doesn't exist yet, expect a 404 + if w.Code != http.StatusNotFound { + t.Errorf("got return code: %d, want: %d", w.Code, http.StatusNotFound) + } + + // create a fake test token file + testTokenContent := "test token" + os.WriteFile(tmpToken, []byte(testTokenContent), 0644) + + // retry calling the handler, and now it should return the token file content + w = httptest.NewRecorder() + ah.getToken(w, req) + data, err := io.ReadAll(w.Result().Body) + if err != nil { + t.Error(err) + } + + if w.Code != http.StatusOK { + t.Errorf("got return code: %d, want: %d", w.Code, http.StatusOK) + } + if string(data) != testTokenContent { + t.Errorf("got content: %v, want: %s", testTokenContent, string(data)) + } +} diff --git a/launcher/util.go b/launcher/util.go new file mode 100644 index 00000000..1e3cd063 --- /dev/null +++ b/launcher/util.go @@ -0,0 +1,30 @@ +package launcher + +import ( + "context" + "fmt" + + "google.golang.org/api/impersonate" + "google.golang.org/api/option" +) + +// FetchImpersonatedToken return an access token for the impersonated service account. +func FetchImpersonatedToken(ctx context.Context, serviceAccount string, audience string, opts ...option.ClientOption) ([]byte, error) { + config := impersonate.IDTokenConfig{ + Audience: audience, + TargetPrincipal: serviceAccount, + IncludeEmail: true, + } + + tokenSource, err := impersonate.IDTokenSource(ctx, config, opts...) + if err != nil { + return nil, fmt.Errorf("error creating token source: %v", err) + } + + token, err := tokenSource.Token() + if err != nil { + return nil, fmt.Errorf("error retrieving token: %v", err) + } + + return []byte(token.AccessToken), nil +} diff --git a/launcher/util_test.go b/launcher/util_test.go new file mode 100644 index 00000000..2e3450bc --- /dev/null +++ b/launcher/util_test.go @@ -0,0 +1,81 @@ +package launcher + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "testing" + + "google.golang.org/api/option" +) + +var expectedEmail = "test2@google.com" +var expectedToken = []byte("test_token") +var expectedURL = fmt.Sprintf(idTokenEndpoint, expectedEmail) + +var testClient = &http.Client{ + Transport: &testRoundTripper{ + roundTripFunc: func(req *http.Request) *http.Response { + if req.URL.String() != expectedURL { + return &http.Response{ + StatusCode: http.StatusNotFound, + } + } + resp := idTokenResp{ + Token: string(expectedToken), + } + respBody, err := json.Marshal(resp) + if err != nil { + return &http.Response{ + StatusCode: http.StatusInternalServerError, + } + } + return &http.Response{ + StatusCode: http.StatusOK, + Header: make(http.Header), + Body: io.NopCloser(bytes.NewBuffer(respBody)), + } + }, + }, +} + +func TestFetchImpersonatedToken(t *testing.T) { + token, err := FetchImpersonatedToken(context.Background(), expectedEmail, "test_aud", option.WithHTTPClient(testClient)) + if err != nil { + t.Fatalf("fetchImpersonatedToken returned error: %v", err) + } + + if !bytes.Equal(token, expectedToken) { + t.Errorf("fetchImpersonatedToken did not return expected token: got %v, want %v", token, expectedToken) + } +} + +func TestFetchImpersonatedTokenNilAud(t *testing.T) { + _, err := FetchImpersonatedToken(context.Background(), expectedEmail, "", option.WithHTTPClient(testClient)) + if err == nil || !strings.Contains(err.Error(), "audience") { + t.Fatalf("got %v error, want audience error", err) + } +} + +func TestFetchImpersonatedTokenBadEmail(t *testing.T) { + _, err := FetchImpersonatedToken(context.Background(), "", "test_aud", option.WithHTTPClient(testClient)) + if err == nil || strings.Contains(err.Error(), "audience") { + t.Fatalf("got %v error, want creating token source error", err) + } +} + +type testRoundTripper struct { + roundTripFunc func(*http.Request) *http.Response +} + +func (t *testRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) { + return t.roundTripFunc(req), nil +} + +type idTokenResp struct { + Token string `json:"token"` +} diff --git a/launcher/verifier/client.go b/launcher/verifier/client.go index 15469de5..ec08f546 100644 --- a/launcher/verifier/client.go +++ b/launcher/verifier/client.go @@ -33,6 +33,8 @@ type VerifyAttestationRequest struct { GcpCredentials [][]byte Attestation *attestpb.Attestation ContainerImageSignatures []oci.Signature + CustomAudience string + CustomNonce []string } // VerifyAttestationResponse is the response from a successful diff --git a/launcher/verifier/rest/rest.go b/launcher/verifier/rest/rest.go index 22fea85a..1769c2e3 100644 --- a/launcher/verifier/rest/rest.go +++ b/launcher/verifier/rest/rest.go @@ -180,6 +180,10 @@ func convertRequestToREST(request verifier.VerifyAttestationRequest) *confidenti ConfidentialSpaceInfo: &confidentialcomputingpb.ConfidentialSpaceInfo{ SignedEntities: []*confidentialcomputingpb.SignedEntity{{ContainerImageSignatures: signatures}}, }, + TokenOptions: &confidentialcomputingpb.TokenOptions{ + Audience: request.CustomAudience, + Nonce: request.CustomNonce, + }, } } diff --git a/launcher/verifier/rest/rest_network_test.go b/launcher/verifier/rest/rest_network_test.go index bb56688b..2198ef2a 100644 --- a/launcher/verifier/rest/rest_network_test.go +++ b/launcher/verifier/rest/rest_network_test.go @@ -49,8 +49,8 @@ func TestWithAgent(t *testing.T) { tpm := test.GetTPM(t) defer client.CheckedClose(t, tpm) - agent := agent.CreateAttestationAgent(tpm, client.AttestationKeyECC, vClient, testPrincipalIDTokenFetcher, signaturediscovery.NewFakeClient(), spec.LaunchSpec{}, log.Default()) - token, err := agent.Attest(context.Background()) + a := agent.CreateAttestationAgent(tpm, client.AttestationKeyECC, vClient, testPrincipalIDTokenFetcher, signaturediscovery.NewFakeClient(), spec.LaunchSpec{}, log.Default()) + token, err := a.Attest(context.Background(), agent.AttestAgentOpts{}) if err != nil { t.Errorf("failed to attest to Attestation Service: %v", err) }