├── acond ├── build ├── alpine_linker ├── build_static ├── .gitignore ├── build.rs ├── Cargo.toml ├── src │ ├── main.rs │ ├── vsock_incoming.rs │ ├── unix_incoming.rs │ ├── image.rs │ ├── oidc.rs │ ├── mount.rs │ └── config.rs └── proto │ └── acon.proto ├── aconcli ├── acon-startvm ├── proto │ └── acon.proto ├── testdata │ ├── test_repo │ │ ├── acon_image_layer0 │ │ ├── acon_image_layer1 │ │ ├── acon_image_layer3 │ │ ├── acon_image_layer2 │ │ └── acon.json │ ├── set1 │ │ ├── cert.der │ │ ├── gen_sign_verify_scripts │ │ │ ├── cert.der │ │ │ ├── sig-sha384 │ │ │ ├── generate.sh │ │ │ ├── test.sh │ │ │ └── priv.pem │ │ ├── priv.pem │ │ └── sample.json │ └── set2 │ │ ├── cert.der │ │ ├── gen_sign_verify_scripts │ │ ├── generate.sh │ │ └── test.sh │ │ ├── pub.pem │ │ ├── sample.json │ │ └── priv.pem ├── .gitignore ├── main.go ├── proto_gen.go ├── cryptoutil │ ├── key_test.go │ ├── hash_test.go │ ├── hash.go │ └── key.go ├── config │ └── config.go ├── attest │ ├── attest_match_quote_test.go │ ├── attest_get_rtmr_test.go │ └── attest.go ├── cmd │ ├── common.go │ ├── root.go │ ├── prune.go │ ├── import.go │ ├── login.go │ ├── logout.go │ ├── stop.go │ ├── init.go │ ├── alias-substitute.go │ ├── sign.go │ ├── hash.go │ ├── restart.go │ ├── kill.go │ ├── export.go │ ├── invoke.go │ ├── remove.go │ ├── shutdown.go │ ├── status.go │ ├── ls.go │ ├── generate.go │ └── report.go ├── service │ ├── client.go │ ├── auth.go │ └── aconclient_grpc.go ├── go.mod ├── repo │ ├── bundle.go │ └── repo_bundle_test.go ├── README.md ├── fileutil │ └── fileutil.go ├── vm │ └── vm.go └── netconn │ └── connection.go ├── doc └── config-acon ├── scripts ├── .gitignore ├── rustc-wrapper.sh ├── start-qemu.sh ├── acon-startvm └── start-happypath.sh ├── samples └── quote │ ├── .gitignore │ ├── client │ ├── app │ ├── go.mod │ └── main.go │ ├── server │ └── Dockerfile │ ├── Makefile │ └── README.md ├── security.md ├── .github └── workflows │ ├── cicd-mongo.yml │ ├── cicd-redis.yml │ ├── cicd-nginx.yml │ ├── cicd-mysql.yml │ ├── cicd-postgresq.yml │ ├── cicd-busybox.yml │ ├── slim.yml │ └── alpine.yml ├── CONTRIBUTING.md ├── sdk └── include │ └── acon_client.h ├── README.md └── CODE_OF_CONDUCT.md /acond/build: -------------------------------------------------------------------------------- 1 | ../scripts/rustc-wrapper.sh -------------------------------------------------------------------------------- /aconcli/acon-startvm: -------------------------------------------------------------------------------- 1 | ../scripts/acon-startvm -------------------------------------------------------------------------------- /acond/alpine_linker: -------------------------------------------------------------------------------- 1 | ../scripts/rustc-wrapper.sh -------------------------------------------------------------------------------- /acond/build_static: -------------------------------------------------------------------------------- 1 | ../scripts/rustc-wrapper.sh -------------------------------------------------------------------------------- /aconcli/proto/acon.proto: -------------------------------------------------------------------------------- 1 | ../../acond/proto/acon.proto -------------------------------------------------------------------------------- /aconcli/testdata/test_repo/acon_image_layer0: -------------------------------------------------------------------------------- 1 | layer 0 2 | -------------------------------------------------------------------------------- /aconcli/testdata/test_repo/acon_image_layer1: -------------------------------------------------------------------------------- 1 | layer 1 2 | -------------------------------------------------------------------------------- /aconcli/testdata/test_repo/acon_image_layer3: -------------------------------------------------------------------------------- 1 | layer 3 2 | -------------------------------------------------------------------------------- /doc/config-acon: -------------------------------------------------------------------------------- 1 | config-6.4.0-rc1-acon-00117-ga324aa0d829e -------------------------------------------------------------------------------- /aconcli/testdata/test_repo/acon_image_layer2: -------------------------------------------------------------------------------- 1 | acon_image_layer1 -------------------------------------------------------------------------------- /aconcli/.gitignore: -------------------------------------------------------------------------------- 1 | aconcli 2 | proto/acon_grpc.pb.go 3 | proto/acon.pb.go 4 | -------------------------------------------------------------------------------- /acond/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Intel Corporation 2 | 3 | target/ 4 | -------------------------------------------------------------------------------- /scripts/.gitignore: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Intel Corporation 2 | 3 | deps/ 4 | -------------------------------------------------------------------------------- /samples/quote/.gitignore: -------------------------------------------------------------------------------- 1 | .acon/ 2 | client/sampleclient 3 | *.json 4 | *.pem 5 | *.cer 6 | -------------------------------------------------------------------------------- /samples/quote/client/app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/ACON/main/samples/quote/client/app -------------------------------------------------------------------------------- /aconcli/testdata/set1/cert.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/ACON/main/aconcli/testdata/set1/cert.der -------------------------------------------------------------------------------- /aconcli/testdata/set2/cert.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/ACON/main/aconcli/testdata/set2/cert.der -------------------------------------------------------------------------------- /aconcli/testdata/set1/gen_sign_verify_scripts/cert.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/ACON/main/aconcli/testdata/set1/gen_sign_verify_scripts/cert.der -------------------------------------------------------------------------------- /aconcli/testdata/set1/gen_sign_verify_scripts/sig-sha384: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/intel/ACON/main/aconcli/testdata/set1/gen_sign_verify_scripts/sig-sha384 -------------------------------------------------------------------------------- /aconcli/testdata/set1/gen_sign_verify_scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | openssl ecparam -name secp521r1 -genkey -out priv.pem 4 | 5 | openssl req -x509 -key priv.pem -outform der -out cert.der 6 | -------------------------------------------------------------------------------- /samples/quote/client/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/acon/sampleclient 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.4 6 | 7 | replace aconcli => ../../../aconcli 8 | 9 | require aconcli v0.0.0-00010101000000-000000000000 10 | -------------------------------------------------------------------------------- /aconcli/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "aconcli/cmd" 8 | "os" 9 | ) 10 | 11 | func main() { 12 | if err := cmd.Cli().Execute(); err != nil { 13 | os.Exit(1) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /aconcli/proto_gen.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | //go:generate protoc --go_out=. --go_opt=paths=source_relative --go_opt=Mproto/acon.proto="./" --go-grpc_out=. --go-grpc_opt=paths=source_relative --go-grpc_opt=Mproto/acon.proto="./" proto/acon.proto 5 | 6 | package main 7 | -------------------------------------------------------------------------------- /aconcli/testdata/set2/gen_sign_verify_scripts/generate.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # generate a private key with 3072 bits key length 4 | openssl genrsa -out priv.pem 3072 5 | 6 | # generate corresponding public key 7 | openssl rsa -in priv.pem -pubout -out pub.pem 8 | 9 | # create a self-signed certificate 10 | openssl req -new -x509 -key priv.pem -outform der -out cert.der -sha384 11 | -------------------------------------------------------------------------------- /security.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation. 3 | 4 | ## Reporting a Vulnerability 5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html). 6 | -------------------------------------------------------------------------------- /acond/build.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | fn main() -> Result<(), Box> { 5 | tonic_build::configure() 6 | .build_server(true) 7 | .build_client(false) 8 | .type_attribute(".", "#[derive(serde::Serialize, serde::Deserialize)]") 9 | .compile(&["proto/acon.proto"], &["proto"])?; 10 | 11 | Ok(()) 12 | } 13 | -------------------------------------------------------------------------------- /aconcli/testdata/set2/gen_sign_verify_scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sign somefile using the private key, and with hash algo $1 (i.e. sha256) 4 | openssl dgst -$1 -sign priv.pem -out sig-$1 generate.sh 5 | echo "Generated signature file" 6 | 7 | echo "Verify using private key" 8 | openssl dgst -$1 -prverify priv.pem -signature sig-$1 generate.sh 9 | 10 | echo "Verify using public key" 11 | openssl dgst -$1 -verify pub.pem -signature sig-$1 generate.sh 12 | -------------------------------------------------------------------------------- /aconcli/testdata/set1/priv.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIHcAgEBBEIB+0nsiefCWt4wug8xnFtrurESMc00FbrSb6vqu2BnTdy1WdgdFceQ 6 | McEhLHUuu22ZOGUn2njfYgdFPjcoC8qt+dGgBwYFK4EEACOhgYkDgYYABAFuW2GD 7 | jX6INhPyjCCy4HOhpQ69WstMswwm9vYJy8u4DjUVaXBxciK4XBucr4RvZ531/6A4 8 | +pVVZ2bDMslbqC8WxQErwj5gJiK2uPlVCejDpXfIbMdLc3XSIXbLKhv6+qdredVn 9 | lFaJ091BVpLxftSitQCCHUxiH/wYPQA8G+vEfRCSpg== 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /aconcli/testdata/set1/gen_sign_verify_scripts/test.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # sign somefile using the private key, and with hash algo $1 (i.e. sha256) 4 | openssl dgst -$1 -sign priv.pem -out sig-$1 generate.sh 5 | echo "Generated signature file" 6 | 7 | echo "Verify using private key" 8 | openssl dgst -$1 -prverify priv.pem -signature sig-$1 generate.sh 9 | 10 | echo "Verify using public key" 11 | openssl dgst -$1 -verify <(openssl x509 -in cert.der -inform der -pubkey -noout) -signature sig-$1 generate.sh 12 | -------------------------------------------------------------------------------- /aconcli/testdata/set1/gen_sign_verify_scripts/priv.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN EC PARAMETERS----- 2 | BgUrgQQAIw== 3 | -----END EC PARAMETERS----- 4 | -----BEGIN EC PRIVATE KEY----- 5 | MIHcAgEBBEIAr8CcKA2xrFAE8XvaECApwNRhRbJetQTrXIY5ZlOvjZwB8dIaGV6S 6 | oP6TXQdKzG7yx/QhHr3mjax7vYWoy1z+2w2gBwYFK4EEACOhgYkDgYYABACbRDNX 7 | +ZEaVA/UIlP8OF11WSi3fEo27iJKAloj3ekmwLTk8XyDIk8bkShfchQAu9YusABc 8 | QtPoJnk8twedRQ24hwET80uTBNWzUXc/INWXO9bkF/4nN1BUUT67gvWXIY5jgW1U 9 | waWdlNif0MXNiMOiqBVeEQrWJDdNMhddo76tGuOZXA== 10 | -----END EC PRIVATE KEY----- 11 | -------------------------------------------------------------------------------- /aconcli/cryptoutil/key_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cryptoutil 5 | 6 | import ( 7 | "testing" 8 | ) 9 | 10 | func TestSignVerify(t *testing.T) { 11 | message := []byte("hello, ACON!") 12 | sig, err := Sign(message, 13 | "../testdata/set2/cert.der", 14 | "../testdata/set2/priv.pem") 15 | if err != nil { 16 | t.Fatal(err) 17 | } 18 | 19 | if !Verify(message, sig, "../testdata/set2/cert.der") { 20 | t.Errorf("Verify failed") 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /samples/quote/server/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright © 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | # 5 | # This file is used by ../Makefile to build the sample server 6 | # 7 | 8 | FROM alpine:latest AS builder 9 | 10 | ARG https_proxy 11 | 12 | RUN https_proxy=${https_proxy} apk update && apk add g++ libc++-static 13 | WORKDIR /work 14 | COPY sdk/ samples/quote/server/ ./ 15 | RUN c++ -std=c++14 -Os -static-pie -flto -Iinclude/ -Wl,--gc-sections,-s src/*.cpp -o quote_server 16 | 17 | FROM scratch 18 | 19 | COPY --from=builder /work/quote_server / 20 | ENTRYPOINT ["/quote_server"] 21 | -------------------------------------------------------------------------------- /aconcli/config/config.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package config 5 | 6 | const ( 7 | RepoDirName = ".acon" 8 | BlobDirName = "blobs" 9 | BlobExtension = ".tar" 10 | PrimaryHashAlgo = "sha384" 11 | PrimaryHashAlgoLen = 96 12 | DockerHashAlgo = "sha256" 13 | ManifestDirName = "manifests" 14 | ManifestFileName = "acon.json" 15 | CertFileName = "signer.cer" 16 | PrivKeyFileName = "private_key.pem" 17 | SignatureFileName = "signature" 18 | ShortHashLen = 12 19 | DefaultCapSize = 0x20000 20 | AconVmPrefix = "aconvm-" 21 | AconVmEnvTag = "__ACONVM=" 22 | ) 23 | -------------------------------------------------------------------------------- /aconcli/testdata/set2/pub.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAw5eqH5W4pwl2Tn7vL3bS 3 | slU5KTv8+Yj6hOSnlm7JJXhyUfjMMmE4RjO0GrcTuMxrcsvXlAlgfsnVgxX7hg5L 4 | 7F/jGrImBd2SK8muWhJLFdGMsKP5sW3s6RVTt1vZixEGP31M7P9180BImdpKHLZA 5 | w/pg176GsaKy1ER9PD/qGQ9TCwtnU6xm23u/zTpvxNi/Np7QolWbobsk0rHCl7by 6 | mFpPB/yhDdPEzgr3TanuiXDA5FLtRPYOdafAsXsAXkakoG5XS6jCX8j1u2LkXqfL 7 | TcA+c3cKi4wi0dq9744206OXEeN1rks8J0NXZkQKkQYObkxLxGqszwv9Da0JGzuz 8 | vyctBg+oRfivx2E4FzReOMNrsz/ebFYemlEsv54yNeerEDLkb3LtMWS5F18yJz7U 9 | q6vIE8prksoTOOpGldmgNgzZqbdNkXNHSt00LnZG2bxBQgR2UrteuHE+BDiqzRqQ 10 | xR85gk1dzqYx4Sm/fbWoMwiVDcrg3P2ogYtZ9avy23C/AgMBAAE= 11 | -----END PUBLIC KEY----- 12 | -------------------------------------------------------------------------------- /.github/workflows/cicd-mongo.yml: -------------------------------------------------------------------------------- 1 | name: CICD-MongoDB 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'doc/**' 8 | pull_request: 9 | branches: [ "*" ] 10 | paths-ignore: 11 | - 'doc/**' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | CICD: 21 | 22 | runs-on: self-hosted 23 | name: CICD-MongoDB 24 | steps: 25 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 26 | - name: Enable MongoDB docker 27 | run: | 28 | source scripts/start-happypath.sh 29 | ATD_MEMSZ=8g run_workload -d test -i mongo -j '.writableFS=true | .uids+=[999] | .entrypoint+=["mongod"]' 30 | -------------------------------------------------------------------------------- /.github/workflows/cicd-redis.yml: -------------------------------------------------------------------------------- 1 | name: CICD-Redis 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'doc/**' 8 | pull_request: 9 | branches: [ "*" ] 10 | paths-ignore: 11 | - 'doc/**' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | CICD: 21 | 22 | runs-on: self-hosted 23 | name: CICD-Redis 24 | steps: 25 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 26 | - name: Enable Redis docker 27 | run: | 28 | source scripts/start-happypath.sh 29 | ATD_MEMSZ=8g run_workload -d test -i redis -j '.writableFS=true | .uids+=[999] | .entrypoint+=["redis-server"]' 30 | -------------------------------------------------------------------------------- /.github/workflows/cicd-nginx.yml: -------------------------------------------------------------------------------- 1 | name: CICD-Nginx 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'doc/**' 8 | pull_request: 9 | branches: [ "*" ] 10 | paths-ignore: 11 | - 'doc/**' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | CICD: 21 | 22 | runs-on: self-hosted 23 | name: CICD-Nginx 24 | steps: 25 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 26 | - name: Enable Nginx docker 27 | run: | 28 | source scripts/start-happypath.sh 29 | run_workload -d test -i acon_nginx -f nginx.dockerfile -j '.uids+=[101] | .writableFS=true | .entrypoint+=["nginx", "-g", "daemon off;"]' 30 | -------------------------------------------------------------------------------- /.github/workflows/cicd-mysql.yml: -------------------------------------------------------------------------------- 1 | name: CICD-MySQL 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'doc/**' 8 | pull_request: 9 | branches: [ "*" ] 10 | paths-ignore: 11 | - 'doc/**' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | CICD: 21 | 22 | runs-on: self-hosted 23 | name: CICD-MySQL 24 | steps: 25 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 26 | - name: Enable MySQL docker 27 | run: | 28 | source scripts/start-happypath.sh 29 | ATD_MEMSZ=8g run_workload -d test -i mysql -j '.writableFS=true | .uids+=[999] | .entrypoint+=["mysqld"] | .env+=["MYSQL_ROOT_PASSWORD=my-secret-pw"]' 30 | -------------------------------------------------------------------------------- /.github/workflows/cicd-postgresq.yml: -------------------------------------------------------------------------------- 1 | name: CICD-PostGreSQL 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'doc/**' 8 | pull_request: 9 | branches: [ "*" ] 10 | paths-ignore: 11 | - 'doc/**' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | CICD: 21 | 22 | runs-on: self-hosted 23 | name: CICD-PostGreSQL 24 | steps: 25 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 26 | - name: Enable PostGreSQL docker 27 | run: | 28 | source scripts/start-happypath.sh 29 | ATD_MEMSZ=8g run_workload -d test -i postgres -j '.writableFS=true | .uids+=[999, 101] | .entrypoint+=["postgres"] | .env+=["POSTGRES_PASSWORD=mysecretpassword"]' 30 | -------------------------------------------------------------------------------- /.github/workflows/cicd-busybox.yml: -------------------------------------------------------------------------------- 1 | name: CICD-BusyBox 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | paths-ignore: 7 | - 'doc/**' 8 | pull_request: 9 | branches: [ "*" ] 10 | paths-ignore: 11 | - 'doc/**' 12 | 13 | env: 14 | CARGO_TERM_COLOR: always 15 | 16 | permissions: 17 | actions: read 18 | checks: read 19 | contents: read 20 | issues: write 21 | pull-requests: write 22 | 23 | jobs: 24 | CICD: 25 | 26 | runs-on: self-hosted 27 | name: CICD-BusyBox 28 | steps: 29 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 # v4.1.0 30 | - name: Enable busybox docker 31 | run: | 32 | source scripts/start-happypath.sh 33 | run_workload -d test -i acon_busybox -f bundle.dockerfile -j '.uids+=[2] | .writableFS=true' -o invoke 34 | -------------------------------------------------------------------------------- /.github/workflows/slim.yml: -------------------------------------------------------------------------------- 1 | name: Debian Builds 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | name: Debian Builds 20 | steps: 21 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 22 | - name: Debian builds 23 | shell: docker build --progress=plain -t acond -f {0} . 24 | run: | 25 | FROM rust:slim 26 | RUN apt update -y && \ 27 | apt upgrade -y && \ 28 | apt install -y pkg-config libssl-dev git musl-tools protobuf-compiler libprotobuf-dev 29 | COPY . ACON 30 | RUN cd ACON/acond && \ 31 | CARGO_BUILD_RUSTFLAGS=-Ctarget-feature=-crt-static cargo build --release 32 | -------------------------------------------------------------------------------- /aconcli/attest/attest_match_quote_test.go: -------------------------------------------------------------------------------- 1 | package attest 2 | 3 | import ( 4 | "encoding/hex" 5 | "flag" 6 | "os" 7 | "strings" 8 | "testing" 9 | ) 10 | 11 | var rtmrlogs = flag.String("logs", "", "rtmr logs") 12 | var quotefile = flag.String("quote", "quote.bin", "file path of binary quote") 13 | 14 | func TestQuoteLogMatch(t *testing.T) { 15 | logs := strings.Split(*rtmrlogs, ",") 16 | mr := hex.EncodeToString(GetRtmrValue(logs)) 17 | 18 | quote, err := os.ReadFile(*quotefile) 19 | if err != nil { 20 | t.Errorf("TestQuoteLogMatch, read quote error: %v", err) 21 | } 22 | quoteStruct, err := ParseQuote(quote) 23 | if err != nil { 24 | t.Errorf("TestQuoteLogMatch, parse quote error: %v", err) 25 | } 26 | r3 := quoteStruct.ReportBody.Rtmr[3] 27 | mrFromQuote := hex.EncodeToString(r3.M[:]) 28 | if mr != mrFromQuote { 29 | t.Errorf("TestQuoteLogMatch, rtmr does not match") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /aconcli/attest/attest_get_rtmr_test.go: -------------------------------------------------------------------------------- 1 | package attest 2 | 3 | import ( 4 | "encoding/hex" 5 | "testing" 6 | ) 7 | 8 | func TestGetRtmrValue(t *testing.T) { 9 | logsAndRtmrs := []struct { 10 | logs []string 11 | rtmr string 12 | }{ 13 | {[]string{"INIT sha384/000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", "github.com/intel/ACON AddManifest sha384/7ec795c3fe89687d4514a1e4f95b2421012fd0b877eba9d5d5f33a1314fe7e10a5b38b0890d890df59b716f55f41e2ff/1be479a625d00cc800da463a19baf111a5f69fa1858d78d124813236bc5b198bd551648273a0f65824b13490263362ef"}, 14 | "e7d7d201073316d7e782aa1db45f91a572321053001fb67793d88351770e4c34552533409091748f39b367c5e254a013", 15 | }, 16 | } 17 | 18 | for _, e := range logsAndRtmrs { 19 | result := hex.EncodeToString(GetRtmrValue(e.logs)) 20 | if result != e.rtmr { 21 | t.Errorf("GetRtmrValue, want: %s, got: %s", e.rtmr, result) 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /aconcli/cmd/common.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "encoding/json" 8 | "errors" 9 | ) 10 | 11 | var ( 12 | dockerImageId string 13 | manifestFile string 14 | privFile string 15 | certFile string 16 | sigFile string 17 | exportName string 18 | targetDir string 19 | env []string 20 | cid uint32 21 | timeout uint64 22 | nologin bool 23 | supportHashAlgo = []string{"sha384", "sha512"} 24 | errorNoRepoFound = errors.New("No ACON repository found. May use 'aconcli init' to create one") 25 | errorRepoExists = errors.New("ACON repository already exists") 26 | ) 27 | 28 | func canonicalJson(data []byte) ([]byte, error) { 29 | var v interface{} 30 | if err := json.Unmarshal(data, &v); err != nil { 31 | return nil, err 32 | } 33 | return json.Marshal(v) 34 | } 35 | -------------------------------------------------------------------------------- /aconcli/testdata/test_repo/acon.json: -------------------------------------------------------------------------------- 1 | { 2 | "aconSpecVersion": [ 3 | 0, 4 | 0 5 | ], 6 | "layers": [ 7 | "sha384/de9d9395ea0a434af3ffd9ae13c9ecf0b8a209881a7a6785b5f5427c0b613bab5c8dbb37bb577ae484cf9d90b13ef5f3", 8 | "sha384/86a35cf623699eaa741712b80d342a7aa350ceb445aaf7c550199737635619e25038027a72db005672ba30f47ea39204", 9 | "sha384/badd0ba0ce62b587b507ad0d4451040f7c7d3bbbb81a572c6723f8cf8b7b081ea81b6b7c81cbfd31fa934ba77c809365" 10 | ], 11 | "aliases": {}, 12 | "entrypoint": [ 13 | "/lib/acon/entrypoint.d/start" 14 | ], 15 | "env": [ 16 | "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 17 | ], 18 | "workingDir": "", 19 | "uids": [], 20 | "logFDs": [], 21 | "writableFS": false, 22 | "noRestart": false, 23 | "signals": [], 24 | "maxInstances": 1, 25 | "policy": { 26 | "rejectUnaccepted": false 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.github/workflows/alpine.yml: -------------------------------------------------------------------------------- 1 | name: Small TCB builds 2 | 3 | on: 4 | push: 5 | branches: [ "*" ] 6 | pull_request: 7 | branches: [ "*" ] 8 | 9 | env: 10 | CARGO_TERM_COLOR: always 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | build: 17 | 18 | runs-on: ubuntu-latest 19 | name: Small TCB builds 20 | steps: 21 | - uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608 22 | - name: Alpine build 23 | shell: docker build --progress=plain -t acond -f {0} . 24 | run: | 25 | FROM rust:alpine 26 | RUN apk add --no-cache musl-dev openssl-dev openssl-libs-static protobuf-dev git 27 | COPY . ACON 28 | RUN cd ACON/acond && \ 29 | CARGO_BUILD_RUSTFLAGS=-Crelocation-model=static cargo build --release --target x86_64-unknown-linux-musl && \ 30 | cargo clean && \ 31 | CARGO_BUILD_RUSTFLAGS=-Ctarget-feature=-crt-static cargo build --release 32 | -------------------------------------------------------------------------------- /aconcli/cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import "github.com/spf13/cobra" 7 | 8 | // rootCmd represents the base command when called without any subcommands 9 | var rootCmd = &cobra.Command{ 10 | Use: "aconcli", 11 | Short: "ACON (Attested Container) Command Line Interface", 12 | Long: ` 13 | Creates/Manages ACON (Attested Container) images and ACON virtual machines. 14 | `, 15 | } 16 | 17 | func Cli() *cobra.Command { 18 | return rootCmd 19 | } 20 | 21 | func init() { 22 | rootCmd.AddGroup( 23 | &cobra.Group{"image", "ACON Image and Image Repo Commands:"}, 24 | &cobra.Group{"runtime", "ACON TD/VM and Container Commands:"}) 25 | rootCmd.PersistentFlags().StringVarP(&targetDir, "directory", "C", "", "change working directory before performing any operations") 26 | rootCmd.PersistentFlags().BoolVar(&nologin, "nologin", false, "if set, login as an anonymous user") 27 | } 28 | -------------------------------------------------------------------------------- /aconcli/service/client.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | type AconStatus struct { 4 | ContainerId uint32 `json:"container_id"` 5 | State uint32 `json:"state"` 6 | Wstatus int32 `json:"wstatus"` 7 | ImageId string `json:"image_id"` 8 | ExePath string `json:"exe_path"` 9 | } 10 | 11 | type AconClient interface { 12 | AddManifest(manifestPath, sigPath, certPath string) (string, []string, error) 13 | AddBlob(alg uint32, blobpath string) error 14 | Finalize() error 15 | Start(imageId string, env []string) (uint32, error) 16 | Kill(cid uint32, signum int32) error 17 | Restart(cid uint32, timeout uint64) error 18 | Invoke(cid uint32, invocation []string, timeout uint64, 19 | env []string, datafile string, capSize uint64) ([]byte, []byte, error) 20 | Inspect(cid uint32) ([]AconStatus, error) 21 | Report(nonceLow, nonceHigh uint64, reportType uint32) (data []byte, mrlog0 []string, 22 | mrlog1 []string, mrlog2 []string, 23 | mrlog3 []string, attestData string, 24 | e error) 25 | } 26 | -------------------------------------------------------------------------------- /samples/quote/Makefile: -------------------------------------------------------------------------------- 1 | # Copyright © 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | ACONCLI ?= aconcli 5 | OPENSSL ?= openssl 6 | DOCKER ?= docker 7 | GO ?= go 8 | SERVER ?= sampleserver 9 | 10 | all: server.json client/sampleclient 11 | 12 | server.json: server/Dockerfile $(wildcard server/src/*cpp) signer.pem signer.cer | .acon/ 13 | $(DOCKER) build -t $(SERVER) -f $< --build-arg https_proxy $(PWD)/../.. 14 | $(ACONCLI) generate -i $(SERVER) $@ 15 | $(ACONCLI) sign -k signer.pem -c signer.cer $@ 16 | 17 | %.pem: 18 | $(OPENSSL) ecparam -genkey -name secp384r1 -out $@ 19 | 20 | %.cer: %.pem 21 | $(OPENSSL) req -x509 -sha384 -key $< -out $@ -outform der -subj /CN=self-signed-$< 22 | 23 | client/sampleclient: 24 | CGO_ENABLED=0 $(GO) -C $(@D) build -v 25 | 26 | .acon/: 27 | $(ACONCLI) init 28 | 29 | clean-server: 30 | rm -rf .acon/ *.json *.pem *.cer 31 | $(DOCKER) rmi -f $(SERVER) 32 | 33 | clean: clean-server 34 | $(GO) -C client $@ 35 | 36 | .PHONY: all clean-server clean 37 | -------------------------------------------------------------------------------- /aconcli/cmd/prune.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "aconcli/repo" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var pruneCmd = &cobra.Command{ 15 | Use: "prune", 16 | Short: "Prune unreferenced file system layers", 17 | GroupID: "image", 18 | Long: ` 19 | Prune unused file system layers from the current ACON image repo, whose path is 20 | determined by the current working directory. 21 | `, 22 | RunE: func(cmd *cobra.Command, args []string) error { 23 | return pruneBlobs() 24 | }, 25 | } 26 | 27 | func pruneBlobs() error { 28 | startingDir := "." 29 | if targetDir != "" { 30 | startingDir = targetDir 31 | } 32 | r, err := repo.FindRepo(startingDir) 33 | if err != nil { 34 | fmt.Fprintf(os.Stderr, "Prune: %v\n", err) 35 | return err 36 | } 37 | 38 | if err := r.Prune(); err != nil { 39 | fmt.Fprintf(os.Stderr, "Prune: %v\n", err) 40 | return err 41 | } 42 | 43 | return nil 44 | } 45 | 46 | func init() { 47 | rootCmd.AddCommand(pruneCmd) 48 | } 49 | -------------------------------------------------------------------------------- /aconcli/cmd/import.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | 10 | "aconcli/repo" 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var importCmd = &cobra.Command{ 15 | Use: "import tarball...", 16 | Short: "Import ACON images from tarballs", 17 | GroupID: "image", 18 | Long: ` 19 | Import ACON images from tarballs (created by 'aconcli export') into the current 20 | ACON image repo, whose path is determined by the current working directory. 21 | `, 22 | Args: cobra.ExactArgs(1), 23 | RunE: func(cmd *cobra.Command, args []string) error { 24 | return importManifest(args) 25 | }, 26 | } 27 | 28 | func importManifest(args []string) error { 29 | startingDir := "." 30 | if targetDir != "" { 31 | startingDir = targetDir 32 | } 33 | r, err := repo.FindRepo(startingDir) 34 | if err != nil { 35 | fmt.Fprintf(os.Stderr, "Import Manifest: %v\n", err) 36 | return err 37 | } 38 | 39 | if err := r.ImportBundle(args); err != nil { 40 | fmt.Fprintf(os.Stderr, "Import Manifest: %v\n", err) 41 | return err 42 | } 43 | return nil 44 | } 45 | 46 | func init() { 47 | rootCmd.AddCommand(importCmd) 48 | } 49 | -------------------------------------------------------------------------------- /aconcli/cmd/login.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "os/user" 8 | 9 | "aconcli/service" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var loginCmd = &cobra.Command{ 14 | Use: "login", 15 | Short: "log in the ACON TD/VM", 16 | GroupID: "runtime", 17 | Long: ` 18 | Log in the specified ACON TD/VM for the current user. 19 | `, 20 | RunE: func(cmd *cobra.Command, args []string) error { 21 | return login() 22 | }, 23 | } 24 | 25 | func login() error { 26 | c, err := service.NewAconHttpConnWithOpts(vmConnTarget, service.OptDialTLSContextInsecure()) 27 | if err != nil { 28 | return fmt.Errorf("Login: cannot connect to %s: %v", vmConnTarget, err) 29 | } 30 | user, err := user.Current() 31 | if err != nil { 32 | return fmt.Errorf("Login: cannot get the current user: %v", err) 33 | } 34 | if err := c.Login(user.Uid); err != nil { 35 | return fmt.Errorf("Login: cannot call 'login' service: %v", err) 36 | } 37 | return nil 38 | } 39 | 40 | func init() { 41 | rootCmd.AddCommand(loginCmd) 42 | loginCmd.Flags().StringVarP(&vmConnTarget, "connect", "c", "", 43 | "protocol/address of the ACON TD/VM") 44 | loginCmd.MarkFlagRequired("connect") 45 | } 46 | -------------------------------------------------------------------------------- /aconcli/cmd/logout.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "os/user" 8 | 9 | "aconcli/service" 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var logoutCmd = &cobra.Command{ 14 | Use: "logout", 15 | Short: "log out the ACON TD/VM", 16 | GroupID: "runtime", 17 | Long: ` 18 | Log out the specified ACON TD/VM for the current user. If not logged in, 19 | this command has no effect. 20 | `, 21 | RunE: func(cmd *cobra.Command, args []string) error { 22 | return logout() 23 | }, 24 | } 25 | 26 | func logout() error { 27 | c, err := service.NewAconHttpConnWithOpts(vmConnTarget, service.OptDialTLSContextInsecure()) 28 | if err != nil { 29 | return fmt.Errorf("Logout: cannot connect to %s: %v", vmConnTarget, err) 30 | } 31 | user, err := user.Current() 32 | if err != nil { 33 | return fmt.Errorf("Logout: cannot get the current user: %v", err) 34 | } 35 | if err := c.Logout(user.Uid); err != nil { 36 | return fmt.Errorf("Logout: cannot call 'logout' service: %v", err) 37 | } 38 | return nil 39 | } 40 | 41 | func init() { 42 | rootCmd.AddCommand(logoutCmd) 43 | logoutCmd.Flags().StringVarP(&vmConnTarget, "connect", "c", "", 44 | "protocol/address of the ACON TD/VM") 45 | logoutCmd.MarkFlagRequired("connect") 46 | } 47 | -------------------------------------------------------------------------------- /aconcli/cmd/stop.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strconv" 10 | 11 | "github.com/spf13/cobra" 12 | ) 13 | 14 | var stopCmd = &cobra.Command{ 15 | Use: "stop ...", 16 | Short: "Stop ACON containers", 17 | GroupID: "runtime", 18 | Long: ` 19 | Stop ACON containers in an ACON TD/VM. 20 | 21 | The ACON TD/VM must be specified by the '-c' flag while the ACON container must 22 | be specified by the '-e' flag. Use 'aconcli status' to list ACON TDs/VMs and 23 | ACON containers running in them. 24 | `, 25 | RunE: func(cmd *cobra.Command, args []string) error { 26 | return stopAcons(args) 27 | }, 28 | } 29 | 30 | func stopAcons(args []string) error { 31 | conn := args[0] 32 | aconIds := make([]uint32, len(args[1:])) 33 | for i, idstr := range args[1:] { 34 | if id, err := strconv.ParseUint(idstr, 10, 32); err == nil { 35 | aconIds[i] = uint32(id) 36 | } else { 37 | fmt.Fprintf(os.Stderr, "Stop: cannot parse ACON container ID %s: %v\n", idstr, err) 38 | continue 39 | } 40 | } 41 | 42 | if err := stopAconInVM(conn, aconIds); err != nil { 43 | fmt.Fprintf(os.Stderr, "Stop: cannot stop ACON container: %v\n", err) 44 | return err 45 | } 46 | return nil 47 | } 48 | 49 | func init() { 50 | rootCmd.AddCommand(stopCmd) 51 | } 52 | -------------------------------------------------------------------------------- /scripts/rustc-wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2023 Intel Corporation 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | cc_hack() { 6 | local a 7 | set -- $CC_HACK_PREPEND $(for arg; do 8 | a= 9 | for sub in $CC_HACK_SUBSTITUTE; do 10 | test "$sub" = "${sub#$arg=>}" || a="$a ${sub#$arg=>}" 11 | done 12 | printf '%s\n' ${a:-$arg} 13 | done) $CC_HACK_APPEND 14 | 15 | exec ${CC:-cc} "$@" 16 | } 17 | 18 | alpine_linker() { 19 | CC_HACK_SUBSTITUTE="-lgcc_s=>-lgcc_eh" cc_hack "$@" 20 | } 21 | 22 | build_with_os_overrides() { 23 | local readonly rsfl=$(printf '%s\n' \ 24 | $(for p in linker; do 25 | test -x ${0%/*}/${ID}_$p && echo "-C$p=$(readlink -f ${0%/*})/${ID}_$p" 26 | done) \ 27 | $CARGO_BUILD_RUSTFLAGS | 28 | sort -u) 29 | 30 | CARGO_BUILD_RUSTFLAGS=$rsfl exec cargo build "$@" 31 | } 32 | 33 | build_static_musl() { 34 | build_with_os_overrides "$@" 35 | } 36 | 37 | build_dynamic_musl() { 38 | CARGO_BUILD_RUSTFLAGS=-Ctarget-feature=-crt-static build_with_os_overrides "$@" 39 | } 40 | 41 | build_static_gnu() { 42 | CARGO_BUILD_RUSTFLAGS=-Ctarget-feature=+crt-static build_with_os_overrides "$@" 43 | } 44 | 45 | build_dynamic_gnu() { 46 | build_with_os_overrides "$@" 47 | } 48 | 49 | build() { 50 | cd ${0%/*} && build_${B:-dynamic}_$(rustup show | awk '$NF=="(default)" { gsub(/.+-/,"",$1); print $1 }') "$@" 51 | } 52 | 53 | build_static() { 54 | B=static build "$@" 55 | } 56 | 57 | build_dynamic() { 58 | B=dynamic build "$@" 59 | } 60 | 61 | . /etc/os-release 62 | ${0##*/} "$@" -------------------------------------------------------------------------------- /aconcli/testdata/set1/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers": [ 3 | "signers/sha224/72a802570f2a1759d33e8297f7cebf9c8fb947db8930143ff313b142/SomeSharedLayer:2", 4 | "sha256/8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759", 5 | "sha384/8bf8dbf9f3c6e29660adf60f0eff64e6f2d47bf789d848386e6d2a644d5cf047d2bc06778f88e74f917a2dae41f641b8" 6 | ], 7 | "aliases": { 8 | "contents": { 9 | "sha256/8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759": [ 10 | "SharedLayerA:1", 11 | "SharedLayerA:0" 12 | ], 13 | "sha224/22dde86581f879abc2e77f1fff4a9da0654f846bc2744fe9ad361cb9": [ 14 | "SharedLayerB:0" 15 | ], 16 | "signers/sha224/72a802570f2a1759d33e8297f7cebf9c8fb947db8930143ff313b142/SomeSharedLayer:2": [ 17 | "SharedLayerC:2", 18 | "SharedLayerC:1", 19 | "SharedLayerC:0" 20 | ] 21 | }, 22 | "self": { 23 | ".": [ 24 | "SomeProduct:2", 25 | "SomeProduct:1", 26 | "SomeProduct:0" 27 | ] 28 | } 29 | }, 30 | "runtime": [ 31 | "exec" 32 | ], 33 | "policy": { 34 | "accepts": [ 35 | "signers/sha256/c4c56a4bfe7a48ca831492c67cd68537ba46fd89876262f9a5ee5547466a94a8/ProductA:2", 36 | "signers/sha256/c4c56a4bfe7a48ca831492c67cd68537ba46fd89876262f9a5ee5547466a94a8/ProductB:1", 37 | "signers/sha224/72a802570f2a1759d33e8297f7cebf9c8fb947db8930143ff313b142/ProductC:3", 38 | "sha384/4c1c3713b0c3d7fd36750e51f28c8f123dfcad9e506cbfd5d06432cdf2df4194327442571e805ca5b46c7ab151ead3cd" 39 | ], 40 | "rejects": true 41 | }, 42 | "attributes": { 43 | "product": "SomeProduct", 44 | "svn": 2 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /aconcli/cmd/init.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io/fs" 10 | "os" 11 | "path/filepath" 12 | 13 | "aconcli/config" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var repodir string 18 | 19 | var initCmd = &cobra.Command{ 20 | Use: "init []", 21 | Short: "Initialize ACON image repo", 22 | GroupID: "image", 23 | Long: ` 24 | Initialize an empty ACON image repo in the current working directory. 25 | 26 | An ACON image repo is a directory containing a subdirectory named '.acon', 27 | under which ACON image files reside. 28 | `, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | return createRepo(args) 31 | }, 32 | } 33 | 34 | // Check whether we can create an ACON repository 35 | func checkRepo(rpath string) error { 36 | _, err := os.Stat(rpath) 37 | if err == nil { 38 | return fs.ErrExist 39 | } 40 | if errors.Is(err, fs.ErrNotExist) { 41 | return nil 42 | } 43 | return err 44 | } 45 | 46 | func createRepo(args []string) error { 47 | if targetDir != "" { 48 | repodir = targetDir 49 | } else { 50 | repodir = "." 51 | } 52 | repopath := filepath.Join(repodir, config.RepoDirName) 53 | 54 | if err := checkRepo(repopath); err != nil { 55 | fmt.Fprintf(os.Stderr, "Failed to initialize ACON repository: %v\n", err) 56 | return err 57 | } 58 | 59 | if err := os.MkdirAll(repopath, 0750); err != nil { 60 | fmt.Fprintf(os.Stderr, "Failed to initialize ACON repository: %v\n", err) 61 | return err 62 | } 63 | 64 | fmt.Fprintf(os.Stdout, "Initialized empty ACON repository in %s\n", repopath) 65 | return nil 66 | } 67 | 68 | func init() { 69 | rootCmd.AddCommand(initCmd) 70 | } 71 | -------------------------------------------------------------------------------- /acond/Cargo.toml: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2023 Intel Corporation 2 | # SPDX-License-Identifier: Apache-2.0 3 | 4 | [package] 5 | name = "acond" 6 | version = "0.1.0" 7 | edition = "2018" 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | #prost = "0.11.2" 13 | #tonic = "0.8.2" 14 | #tokio-vsock = "0.4.0" 15 | actix-web = { version = "4.5.1", default-features = false, features = ["openssl"] } 16 | actix-multipart = "0.6.1" 17 | actix-web-lab = "0.20.2" 18 | tokio = { version = "1.17.0", default-features = false, features = ["rt", "fs", "io-std", "signal"] } 19 | futures = "0.3.21" 20 | prctl = "1.0.0" 21 | nix = { version = "0.26.2", default-features = false, features = ["fs", "mman", "mount", "poll", "ioctl", "process", "reboot", "term", "sched", "socket", "user"] } 22 | libc = "0.2.121" 23 | anyhow = { version = "1.0.56", default-features = false } 24 | openssl = "0.10.66" 25 | data-encoding = { version = "2.3.2", default-features = false, features = ["alloc"] } 26 | serde = { version = "1.0.126", default-features = false, features = ["derive"] } 27 | serde_json = { version = "1.0.64", default-features = false, features = ["alloc"] } 28 | bincode = "1.3.3" 29 | tar = "0.4.38" 30 | lazy_static = "1.3.0" 31 | scopeguard = "1.0.0" 32 | rustyline = "10.1.1" 33 | walkdir = "2.3.2" 34 | tokio-send-fd = "0.9.3" 35 | openidconnect = "3.5.0" 36 | chrono = "0.4.38" 37 | env_logger = "0.11.3" 38 | log = "0.4.21" 39 | 40 | [dev-dependencies] 41 | # unit test 42 | tempfile = "3.1.0" 43 | 44 | [build-dependencies] 45 | tonic-build = "0.8.2" 46 | 47 | [features] 48 | full = ["interactive"] 49 | interactive = [] 50 | 51 | [profile.release] 52 | opt-level = "s" 53 | strip = true 54 | lto = true 55 | panic = "abort" 56 | codegen-units = 1 57 | -------------------------------------------------------------------------------- /aconcli/testdata/set2/sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "layers": [ 3 | "signers/sha224/72a802570f2a1759d33e8297f7cebf9c8fb947db8930143ff313b142/SomeSharedLayer:2", 4 | "sha256/8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759", 5 | "sha384/8bf8dbf9f3c6e29660adf60f0eff64e6f2d47bf789d848386e6d2a644d5cf047d2bc06778f88e74f917a2dae41f641b8", 6 | "sha256/fe4b5357dae419c2784d35586ccb721a621ba82756022889b9f49f34b5dd5b06" 7 | ], 8 | "aliases": { 9 | "contents": { 10 | "sha256/8d3ac3489996423f53d6087c81180006263b79f206d3fdec9e66f0e27ceb8759": [ 11 | "SharedLayerA:1", 12 | "SharedLayerA:0" 13 | ], 14 | "sha224/22dde86581f879abc2e77f1fff4a9da0654f846bc2744fe9ad361cb9": [ 15 | "SharedLayerB:0" 16 | ], 17 | "sha256/fe4b5357dae419c2784d35586ccb721a621ba82756022889b9f49f34b5dd5b06": [ 18 | "SharedLayerD:0" 19 | ], 20 | "signers/sha224/72a802570f2a1759d33e8297f7cebf9c8fb947db8930143ff313b142/SomeSharedLayer:2": [ 21 | "SharedLayerC:2", 22 | "SharedLayerC:1", 23 | "SharedLayerC:0" 24 | ] 25 | }, 26 | "self": { 27 | ".": [ 28 | "SomeProduct:2", 29 | "SomeProduct:1", 30 | "SomeProduct:0" 31 | ] 32 | } 33 | }, 34 | "runtime": [ 35 | "exec" 36 | ], 37 | "policy": { 38 | "accepts": [ 39 | "signers/sha256/c4c56a4bfe7a48ca831492c67cd68537ba46fd89876262f9a5ee5547466a94a8/ProductA:2", 40 | "signers/sha256/c4c56a4bfe7a48ca831492c67cd68537ba46fd89876262f9a5ee5547466a94a8/ProductB:1", 41 | "signers/sha224/72a802570f2a1759d33e8297f7cebf9c8fb947db8930143ff313b142/ProductC:3", 42 | "sha384/4c1c3713b0c3d7fd36750e51f28c8f123dfcad9e506cbfd5d06432cdf2df4194327442571e805ca5b46c7ab151ead3cd" 43 | ], 44 | "rejects": true 45 | }, 46 | "attributes": { 47 | "product": "SomeProduct", 48 | "svn": 2 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /aconcli/cmd/alias-substitute.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "aconcli/repo" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var substituteAll bool 16 | 17 | var aliasCmd = &cobra.Command{ 18 | Use: "alias-substitute manifest", 19 | Short: "Substitute file system layer digests with aliases", 20 | GroupID: "image", 21 | Long: ` 22 | Substitute digests of file system layers in the specified ACON image manifest 23 | with aliases defined in ACON images/manifests stored in the same ACON image 24 | repo where the specified manifest resides. 25 | 26 | The ACON image repo path is implied by the manifest file path. 27 | 28 | By default, only signed images/manifests are searched for alias definitions 29 | unless '-a' is specified, in which case both signed and unsigned manifests are 30 | searched. 31 | `, 32 | Args: cobra.ExactArgs(1), 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | return aliasSubstitute(args) 35 | }, 36 | } 37 | 38 | func aliasSubstitute(args []string) error { 39 | manifestFile = args[0] 40 | startingDir := filepath.Dir(manifestFile) 41 | if targetDir != "" { 42 | startingDir = targetDir 43 | } 44 | r, err := repo.FindRepo(startingDir) 45 | if err != nil { 46 | fmt.Fprintf(os.Stderr, "Alias Substitution: %v\n", err) 47 | return err 48 | } 49 | 50 | var filter func(*repo.Bundle) bool 51 | if !substituteAll { 52 | filter = func(b *repo.Bundle) bool { 53 | return b.IsSignatureValid() 54 | } 55 | } 56 | if err := r.Alias(manifestFile, filter); err != nil { 57 | fmt.Fprintf(os.Stderr, "Alias Substitution: %v\n", err) 58 | return err 59 | } 60 | return nil 61 | } 62 | 63 | func init() { 64 | rootCmd.AddCommand(aliasCmd) 65 | aliasCmd.Flags().BoolVarP(&substituteAll, "all", "a", false, 66 | "search both signed and unsigned manifests for alias definitions") 67 | } 68 | -------------------------------------------------------------------------------- /aconcli/cmd/sign.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "aconcli/repo" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var resign bool = false 16 | 17 | var signCmd = &cobra.Command{ 18 | Use: "sign manifest", 19 | Short: "Sign an ACON image", 20 | GroupID: "image", 21 | Long: ` 22 | Sign the specified ACON image/manifest and store the signature in the ACON 23 | image repo. 24 | 25 | When signing a manifest for the first time, both a private key file and its 26 | corresponding certificate file must be specified. The certificate file is used 27 | to determine the hash algorithm when creating the digital signature. 'aconcli 28 | sign' keeps symlinks (in the ACON image repo) to the private key and 29 | certificate files to facilitate future re-signing. 30 | 31 | When re-signing a manifest, 'aconcli sign' reuses the private key and 32 | certifcate files by default, and can be overridden by respective command line 33 | flags. 34 | `, 35 | Args: cobra.ExactArgs(1), 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | return signManifest(args) 38 | }, 39 | } 40 | 41 | func signManifest(args []string) error { 42 | manifestFile := args[0] 43 | startingDir := filepath.Dir(manifestFile) 44 | if targetDir != "" { 45 | startingDir = targetDir 46 | } 47 | r, err := repo.FindRepo(startingDir) 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, "Sign Manifest: %v\n", err) 50 | return err 51 | } 52 | 53 | if err := r.CommitManifest(manifestFile, certFile, privFile); err != nil { 54 | fmt.Fprintf(os.Stderr, "Sign Manifest: cannot sign manifest %s: %v\n", manifestFile, err) 55 | return err 56 | } 57 | return nil 58 | } 59 | 60 | func init() { 61 | rootCmd.AddCommand(signCmd) 62 | signCmd.Flags().StringVarP(&privFile, "key", "k", "", 63 | "path of the private key file") 64 | signCmd.Flags().StringVarP(&certFile, "cert", "c", "", 65 | "path of the certificate file to get the hash algorithm from") 66 | } 67 | -------------------------------------------------------------------------------- /aconcli/cmd/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "encoding/hex" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | 12 | "aconcli/cryptoutil" 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var hashCmd = &cobra.Command{ 17 | Use: "hash certificate [manifest]...", 18 | Short: "Compute SignerID and ImageIDs", 19 | Long: ` 20 | Compute the digests of the specified certificate and manifest files using the 21 | hash algorithm deduced from the certificate file. 22 | 23 | Outputs from 'aconcli hash' are the SignerID of the certificate file and the 24 | ImageIDs of the manifest files as if signed by that certificate. 25 | `, 26 | Args: cobra.MinimumNArgs(1), 27 | RunE: func(cmd *cobra.Command, args []string) error { 28 | return doHash(args) 29 | }, 30 | } 31 | 32 | func doHash(args []string) error { 33 | certFile := args[0] 34 | files := args[1:] 35 | 36 | certDigest, hashAlgo, err := cryptoutil.GetCertDigest(certFile) 37 | if err != nil { 38 | fmt.Fprintf(os.Stderr, "Failed to get hash algorithm and digest for %s: %v\n", certFile, err) 39 | return err 40 | } 41 | fmt.Fprintf(os.Stdout, "%s/%v\t%s\n", hashAlgo, hex.EncodeToString(certDigest), certFile) 42 | 43 | for _, file := range files { 44 | content, err := os.ReadFile(filepath.Clean(file)) 45 | if err != nil { 46 | fmt.Fprintf(os.Stderr, "Failed to read file %s: %v\n", file, err) 47 | continue 48 | } 49 | 50 | content, err = canonicalJson(content) 51 | if err != nil { 52 | fmt.Fprintf(os.Stderr, "Failed to canonicalize file %s: %v\n", file, err) 53 | continue 54 | } 55 | 56 | manifestDigest, err := cryptoutil.BytesDigest(content, hashAlgo) 57 | if err != nil { 58 | fmt.Fprintf(os.Stderr, "Failed to get digest for %s: %v\n", file, err) 59 | continue 60 | } 61 | fmt.Fprintf(os.Stdout, "%s/%v/%v\t%s\n", hashAlgo, 62 | hex.EncodeToString(certDigest), hex.EncodeToString(manifestDigest), file) 63 | } 64 | return nil 65 | } 66 | 67 | func init() { 68 | rootCmd.AddCommand(hashCmd) 69 | } 70 | -------------------------------------------------------------------------------- /aconcli/cmd/restart.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "time" 10 | 11 | "aconcli/service" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var restartCmd = &cobra.Command{ 16 | Use: "restart", 17 | Short: "Restart ACON container", 18 | GroupID: "runtime", 19 | Long: ` 20 | Restart the specified ACON container in the specified ACON TD/VM. 21 | 22 | If the specified ACON container is running, 'aconcli restart' would try to stop 23 | it before restarting it. 24 | 25 | The ACON TD/VM must be specified by the '-c' flag while the ACON container must 26 | be specified by the '-e' flag. Use 'aconcli status' to list ACON TDs/VMs and 27 | ACON containers running in them. 28 | `, 29 | RunE: func(cmd *cobra.Command, args []string) error { 30 | return restart(args) 31 | }, 32 | } 33 | 34 | func restart(args []string) error { 35 | opts := []service.Opt{service.OptDialTLSContextInsecure(), 36 | service.OptTimeout(service.DefaultServiceTimeout + time.Duration(timeout)*time.Second)} 37 | if nologin { 38 | opts = append(opts, service.OptNoAuth()) 39 | } 40 | c, err := service.NewAconHttpConnWithOpts(vmConnTarget, opts...) 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "Restart: cannot connect to %s: %v\n", vmConnTarget, err) 43 | return err 44 | } 45 | 46 | err = c.Restart(cid, timeout) 47 | if err != nil { 48 | fmt.Fprintf(os.Stderr, "Restart: cannot call 'restart' service: %v\n", err) 49 | return err 50 | } 51 | return nil 52 | } 53 | 54 | func init() { 55 | rootCmd.AddCommand(restartCmd) 56 | 57 | restartCmd.Flags().StringVarP(&vmConnTarget, "connect", "c", "", 58 | "connection target for the ACON virtual machine") 59 | restartCmd.MarkFlagRequired("connect") 60 | 61 | restartCmd.Flags().Uint32VarP(&cid, "container", "e", 0, 62 | "target ACON container to invoke the command") 63 | restartCmd.MarkFlagRequired("container") 64 | 65 | restartCmd.Flags().Uint64VarP(&timeout, "timeout", "t", 30, 66 | "optional timeout in seconds to wait before restarting the container") 67 | } 68 | -------------------------------------------------------------------------------- /aconcli/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/intel/acon/aconcli 2 | 3 | go 1.21 4 | 5 | toolchain go1.21.4 6 | 7 | replace aconcli => ./ 8 | 9 | require ( 10 | aconcli v0.0.0-00010101000000-000000000000 11 | github.com/docker/docker v27.1.2+incompatible 12 | github.com/mdlayher/vsock v1.2.1 13 | github.com/prometheus/procfs v0.12.0 14 | github.com/spf13/cobra v1.8.0 15 | google.golang.org/grpc v1.59.0 16 | google.golang.org/protobuf v1.33.0 17 | ) 18 | 19 | require ( 20 | github.com/Microsoft/go-winio v0.6.1 // indirect 21 | github.com/distribution/reference v0.5.0 // indirect 22 | github.com/docker/distribution v2.8.3+incompatible // indirect 23 | github.com/docker/go-connections v0.4.0 // indirect 24 | github.com/docker/go-units v0.5.0 // indirect 25 | github.com/felixge/httpsnoop v1.0.4 // indirect 26 | github.com/go-logr/logr v1.4.2 // indirect 27 | github.com/go-logr/stdr v1.2.2 // indirect 28 | github.com/gogo/protobuf v1.3.2 // indirect 29 | github.com/golang/protobuf v1.5.3 // indirect 30 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 31 | github.com/mdlayher/socket v0.5.0 // indirect 32 | github.com/moby/docker-image-spec v1.3.1 // indirect 33 | github.com/moby/term v0.5.0 // indirect 34 | github.com/morikuni/aec v1.0.0 // indirect 35 | github.com/opencontainers/go-digest v1.0.0 // indirect 36 | github.com/opencontainers/image-spec v1.1.0-rc5 // indirect 37 | github.com/pkg/errors v0.9.1 // indirect 38 | github.com/pmezard/go-difflib v1.0.0 // indirect 39 | github.com/spf13/pflag v1.0.5 // indirect 40 | go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect 41 | go.opentelemetry.io/otel v1.28.0 // indirect 42 | go.opentelemetry.io/otel/metric v1.28.0 // indirect 43 | go.opentelemetry.io/otel/trace v1.28.0 // indirect 44 | golang.org/x/mod v0.14.0 // indirect 45 | golang.org/x/net v0.23.0 // indirect 46 | golang.org/x/sync v0.5.0 // indirect 47 | golang.org/x/sys v0.18.0 // indirect 48 | golang.org/x/text v0.14.0 // indirect 49 | golang.org/x/time v0.5.0 // indirect 50 | golang.org/x/tools v0.16.0 // indirect 51 | google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect 52 | gotest.tools/v3 v3.5.1 // indirect 53 | ) 54 | -------------------------------------------------------------------------------- /aconcli/cmd/kill.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "strconv" 10 | 11 | "aconcli/service" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var killCmd = &cobra.Command{ 16 | Use: "kill signo", 17 | Short: "Signal an ACON container", 18 | GroupID: "runtime", 19 | Long: ` 20 | Send the specified signal to the ACON container in the specified ACON TD/VM. 21 | 22 | The ACON TD/VM must be specified by the '-c' flag while the ACON container must 23 | be specified by the '-e' flag. Use 'aconcli status' to list ACON TDs/VMs and 24 | ACON containers running in them. 25 | 26 | 'signo' (signal number) could be a positive or negative integer. A positive 27 | signo will be sent to the container process (PID 1) only, while a negative 28 | signo will cause -signo to be sent to the whole process group led by the 29 | container process. 30 | `, 31 | Args: cobra.ExactArgs(1), 32 | RunE: func(cmd *cobra.Command, args []string) error { 33 | return kill(args) 34 | }, 35 | } 36 | 37 | func kill(args []string) error { 38 | signum, err := strconv.ParseInt(args[0], 10, 32) 39 | if err != nil { 40 | fmt.Fprintf(os.Stderr, "Kill: cannot get signal number from %s: %v\n", args[0], err) 41 | return err 42 | } 43 | opts := []service.Opt{service.OptDialTLSContextInsecure()} 44 | if nologin { 45 | opts = append(opts, service.OptNoAuth()) 46 | } 47 | c, err := service.NewAconHttpConnWithOpts(vmConnTarget, opts...) 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, "Kill: cannot connect to %s: %v\n", vmConnTarget, err) 50 | return err 51 | } 52 | 53 | err = c.Kill(cid, int32(signum)) 54 | if err != nil { 55 | fmt.Fprintf(os.Stderr, "Kill: cannot call 'kill' service: %v\n", err) 56 | return err 57 | } 58 | return nil 59 | } 60 | 61 | func init() { 62 | rootCmd.AddCommand(killCmd) 63 | 64 | killCmd.Flags().StringVarP(&vmConnTarget, "connect", "c", "", 65 | "protocol/address of the ACON TD/VM") 66 | killCmd.MarkFlagRequired("connect") 67 | 68 | killCmd.Flags().Uint32VarP(&cid, "container", "e", 0, 69 | "the ACON container to which the signal will be sent") 70 | killCmd.MarkFlagRequired("container") 71 | } 72 | -------------------------------------------------------------------------------- /aconcli/cmd/export.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | 11 | "aconcli/repo" 12 | 13 | "github.com/spf13/cobra" 14 | ) 15 | 16 | var ignoreSig bool 17 | 18 | var releaseCmd = &cobra.Command{ 19 | Use: "export manifest", 20 | Short: "Export ACON images into tarballs", 21 | GroupID: "image", 22 | Long: ` 23 | Export an ACON image (specified as the path to its manifest) into a tarball. 24 | 25 | An ACON image is comprised of a manifest file, a signature file (containing 26 | the digital signature of the manifest), a certificate file (for verifying the 27 | signature), and the file system layers referenced by the manifest. 'aconcli 28 | export' packages all those files in a single tarball, which can then be 29 | imported into a different ACON image repo by 'aconcli import'. 30 | 31 | NOTE1: Only those file system layers referenced by digests will be exported, 32 | while others referenced by aliases will NOT be exported. 33 | 34 | NOTE2: ACON images that the specified manifest depends on (i.e., images 35 | defining aliases that have been referenced by the specified manifest) will NOT 36 | be exported. 37 | `, 38 | Args: cobra.ExactArgs(1), 39 | RunE: func(cmd *cobra.Command, args []string) error { 40 | return exportBundles(args) 41 | }, 42 | } 43 | 44 | func exportBundles(args []string) error { 45 | if ignoreSig { 46 | return fmt.Errorf("flag '--ignoresig' is yet to be implemented") 47 | } 48 | 49 | manifestFile := args[0] 50 | startingDir := filepath.Dir(manifestFile) 51 | if targetDir != "" { 52 | startingDir = targetDir 53 | } 54 | r, err := repo.FindRepo(startingDir) 55 | if err != nil { 56 | fmt.Fprintf(os.Stderr, "Export Bundle: %v\n", err) 57 | return err 58 | } 59 | 60 | if err := r.ExportBundle(manifestFile, exportName); err != nil { 61 | fmt.Fprintf(os.Stderr, "Export Bundle: %v\n", err) 62 | return err 63 | } 64 | return nil 65 | } 66 | 67 | func init() { 68 | rootCmd.AddCommand(releaseCmd) 69 | releaseCmd.Flags().BoolVarP(&ignoreSig, "ignoresig", "", false, 70 | "ignoring missing signature file while exporting image (unimplemented)") 71 | releaseCmd.Flags().StringVarP(&exportName, "output", "o", "", 72 | "output tarball file name") 73 | releaseCmd.MarkFlagRequired("output") 74 | } 75 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ### License 4 | 5 | is licensed under the terms in [LICENSE]. By contributing to the project, you agree to the license and copyright terms therein and release your contribution under these terms. 6 | 7 | ### Sign your work 8 | 9 | Please use the sign-off line at the end of the patch. Your signature certifies that you wrote the patch or otherwise have the right to pass it on as an open-source patch. The rules are pretty simple: if you can certify 10 | the below (from [developercertificate.org](http://developercertificate.org/)): 11 | 12 | ``` 13 | Developer Certificate of Origin 14 | Version 1.1 15 | 16 | Copyright (C) 2004, 2006 The Linux Foundation and its contributors. 17 | 660 York Street, Suite 102, 18 | San Francisco, CA 94110 USA 19 | 20 | Everyone is permitted to copy and distribute verbatim copies of this 21 | license document, but changing it is not allowed. 22 | 23 | Developer's Certificate of Origin 1.1 24 | 25 | By making a contribution to this project, I certify that: 26 | 27 | (a) The contribution was created in whole or in part by me and I 28 | have the right to submit it under the open source license 29 | indicated in the file; or 30 | 31 | (b) The contribution is based upon previous work that, to the best 32 | of my knowledge, is covered under an appropriate open source 33 | license and I have the right under that license to submit that 34 | work with modifications, whether created in whole or in part 35 | by me, under the same open source license (unless I am 36 | permitted to submit under a different license), as indicated 37 | in the file; or 38 | 39 | (c) The contribution was provided directly to me by some other 40 | person who certified (a), (b) or (c) and I have not modified 41 | it. 42 | 43 | (d) I understand and agree that this project and the contribution 44 | are public and that a record of the contribution (including all 45 | personal information I submit with it, including my sign-off) is 46 | maintained indefinitely and may be redistributed consistent with 47 | this project or the open source license(s) involved. 48 | ``` 49 | 50 | Then you just add a line to every git commit message: 51 | 52 | Signed-off-by: Joe Smith 53 | 54 | Use your real name (sorry, no pseudonyms or anonymous contributions.) 55 | 56 | If you set your `user.name` and `user.email` git configs, you can sign your 57 | commit automatically with `git commit -s`. 58 | -------------------------------------------------------------------------------- /acond/src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | #[macro_use] 4 | extern crate lazy_static; 5 | #[macro_use] 6 | extern crate scopeguard; 7 | 8 | use crate::config::Config; 9 | use anyhow::{anyhow, Result}; 10 | use nix::{ 11 | sys::reboot::{self, RebootMode}, 12 | unistd::{self, ForkResult, Gid, Pid, Uid}, 13 | }; 14 | use std::{fs, os::unix::net::UnixStream}; 15 | use tokio::runtime::Builder; 16 | 17 | mod config; 18 | mod container; 19 | mod image; 20 | mod ipc; 21 | mod mount; 22 | mod oidc; 23 | mod pod; 24 | #[cfg(feature = "interactive")] 25 | mod pty; 26 | mod report; 27 | mod restful; 28 | mod server; 29 | mod utils; 30 | 31 | fn start_service() -> Result<(), Box> { 32 | let mut config = Config::new(); 33 | config.parse_cmdline(None)?; 34 | 35 | let (pstream, cstream) = UnixStream::pair()?; 36 | 37 | match unsafe { unistd::fork() } { 38 | Ok(ForkResult::Parent { child: _ }) => { 39 | pstream.set_nonblocking(true)?; 40 | drop(cstream); 41 | 42 | let rt = Builder::new_current_thread().enable_all().build()?; 43 | rt.block_on(server::start_server(pstream, &config))?; 44 | rt.shutdown_background(); 45 | 46 | Ok(()) 47 | } 48 | Ok(ForkResult::Child) => { 49 | let gid = Gid::from_raw(utils::CLIENT_UID); 50 | let uid = Uid::from_raw(utils::CLIENT_UID); 51 | 52 | fs::create_dir_all(utils::BLOB_DIR)?; 53 | unistd::chown(utils::BLOB_DIR, Some(uid), Some(gid))?; 54 | 55 | unistd::setresgid(gid, gid, gid)?; 56 | unistd::setresuid(uid, uid, uid)?; 57 | 58 | prctl::set_name("deprivileged_server").map_err(|e| anyhow!(e.to_string()))?; 59 | cstream.set_nonblocking(true)?; 60 | drop(pstream); 61 | 62 | let rt = Builder::new_current_thread().enable_all().build()?; 63 | rt.block_on(restful::run_server(cstream, &config))?; 64 | 65 | Ok(()) 66 | } 67 | Err(errno) => { 68 | log::error!("Start service error, errno = {errno}."); 69 | Err("Start service error, errno = {errno}.".into()) 70 | } 71 | } 72 | } 73 | 74 | fn main() -> Result<(), Box> { 75 | env_logger::init(); 76 | mount::mount_rootfs()?; 77 | 78 | start_service()?; 79 | 80 | if unistd::getpid() == Pid::from_raw(1) { 81 | reboot::reboot(RebootMode::RB_POWER_OFF)?; 82 | } 83 | Ok(()) 84 | } 85 | -------------------------------------------------------------------------------- /aconcli/testdata/set2/priv.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIG4gIBAAKCAYEAw5eqH5W4pwl2Tn7vL3bSslU5KTv8+Yj6hOSnlm7JJXhyUfjM 3 | MmE4RjO0GrcTuMxrcsvXlAlgfsnVgxX7hg5L7F/jGrImBd2SK8muWhJLFdGMsKP5 4 | sW3s6RVTt1vZixEGP31M7P9180BImdpKHLZAw/pg176GsaKy1ER9PD/qGQ9TCwtn 5 | U6xm23u/zTpvxNi/Np7QolWbobsk0rHCl7bymFpPB/yhDdPEzgr3TanuiXDA5FLt 6 | RPYOdafAsXsAXkakoG5XS6jCX8j1u2LkXqfLTcA+c3cKi4wi0dq9744206OXEeN1 7 | rks8J0NXZkQKkQYObkxLxGqszwv9Da0JGzuzvyctBg+oRfivx2E4FzReOMNrsz/e 8 | bFYemlEsv54yNeerEDLkb3LtMWS5F18yJz7Uq6vIE8prksoTOOpGldmgNgzZqbdN 9 | kXNHSt00LnZG2bxBQgR2UrteuHE+BDiqzRqQxR85gk1dzqYx4Sm/fbWoMwiVDcrg 10 | 3P2ogYtZ9avy23C/AgMBAAECggGAarZYjxr0v82dyn3eqZq+oaV1plWgU9S0OcU2 11 | 2hBflh384VRItEPum/6hiOHR1MVS6jLkz1O+Xe5omzhnHmC2c6RtF6eSryiQE2vZ 12 | wDKy3JiTldwg4vkChLiqECQAsKr9Ka6HRdaq16h+89HxFXEqLMJyC8NbYZdO0hj7 13 | 6t9ELZ/6ws/Z9ADfrWf64VG8oT1QNdC4K5lha//au0lYzcV23kCHVegzRoP/oBrD 14 | io/oIPxvgSLtaO6GPRLlQu/E56rKOrkHs5XFxp3ecctcxjGa10QYfoV2krjQ+xNv 15 | vv78eB82WZ0ErElYo82Ayx+4Ve7k8Wi4zgRINFk7CUpdzNv5LCG79fLD+dUACB6B 16 | qgg1Wn0H4b5b20nK9xRODQfVnQsqogYKs1SnBtv1wzWgUob1Q7tWfTMLrvi/1W2s 17 | Svmew+9jsnJFWM71DHSdPdcBkFFSdMMFxoQuNOX/SR8sNfLCXc5fCd6JIaaM4094 18 | wYWIGJ9VRdUfDffXjOygpBuMxhqBAoHBAON+tBsChq3Zrzvf65wIIpFJN3Igh1La 19 | unnqZyhIxAUvy3tjk46VF8nu+8skkTVmz3vEfvny8Usza6qb/Y8zM+qiLzHBUHv7 20 | 5pcrRk4r6Nh3wmdOhhGcmqRZiUtiJofvmf4+MBCJ4kndh4YCLeT3MgH+WHB7RAk2 21 | 9kUtr50sRqWAwabyMgB6pw0xttdwms87ffef7jyySMBB+S6MuqDf9De6NgkGMVEZ 22 | Vpbo9vhxvDAoGRinKSKBIwXdh2EKDdGETwKBwQDcGaIC7oHT/Qr9ViJNu5MFnYxF 23 | MoGPIOZL77BQUNm2DfevcsAe3dQVHcwQpuGIebzIctIZGfQ2/5tOdyXmZ11DPnRA 24 | gveAfnKpyrV/TdNR5uTYUBlAet3ogy+hU6dLj7XKYiCWA/l+490PuJPSpuOChjbJ 25 | W90XY8DkZ34Fx6f+lvqkpktI4sLtJ67PTWMh0p7gRrq1QHe0gqJnMfUBMU0/hWXR 26 | GedFDHOplpV6xGlYxJr2++AHiDHwYTtdISh4gJECgcAi2CdR88MZIbx7WULHNV/G 27 | DQbXDjBr+mCRh9uhCfQsLzPlhEz3i9KG9XnPlZxAJ60j8ebY9eMLd6bIDPh7VIZh 28 | K9I6trdTJjQdd11fX85cSYuXMynJzMjK34Pt7eyk6YgWrwPBpLzqP2oglnjdFxer 29 | wn9dSyQSnT5/Pg4l7sArXVtL7NDHzgGEPdcZjH/Juf0DTpW46j3GZNR70sZLGcJY 30 | PsdD/pTTLSRcHPam+xSt85ZUdlYQmOL6ZQH1VQrkynUCgcBrbcd6DtgnlHbsuEH+ 31 | FvPn5cI1dDVwmupNXix8xzm8dozSgfvsFPITbHvSFOI4LtNKM2EBB33/6jrvzZAe 32 | KY2H6M/tPP95JD7rSRnVjTL74usH+fCZXF87wexZne8mHnro0YR+NGpu2OFpbvoX 33 | 84VQjBnppoD/jZz03t7QC2egCClGfDR62tOx1ZCpdmhQdfE+Hse6+EB1Rt3l6Khh 34 | cKL3wjC0IySSu0UpHWrQhVW2EiBhNQdgJcXaQlsrVaffSUECgcAKoSkVTdV1uUYq 35 | spv4ysD+Ktv2fGBrT7nf0ZSnFUMcPZ9ifoYBSmYxQwCOsXZ34ZHALIWFld9Y54wD 36 | aPxn8LjE6mmy+AQkUGMphEk0hxVPU+HnbR9b47YbmndgN1lhCjPhxixiJKi9A2Of 37 | xwxBX4zkpwp8sY7YK60EkHp04y4evH9xk/lGB7BsxzDXcVGH67A6u6rKre3Cpk3z 38 | 6J7qZpXDLkU/wY/mMey1ulJB9pJyZo5ErmKwDl2K60YPpkL/SAQ= 39 | -----END RSA PRIVATE KEY----- 40 | -------------------------------------------------------------------------------- /samples/quote/README.md: -------------------------------------------------------------------------------- 1 | # Remote Attestation Sample Code 2 | 3 | This directory contains sample source code to demonstrate how remote attestation works in ACON containers. More information on TDX remote attestation can be found at https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/TDX_Quoting_Library_API.pdf. 4 | 5 | This sample is comprised of a server and a client that communicate with each other over TCP. The server is an ACON container that generates TDX quotes upon requests from the client, which is a command line application running in the host. 6 | 7 | The client works with the server to retrieve a TD quote, along with the measurement logs and additional attestation data. It then verifies the quote, checks if RTMR values in the quote match those calculated from the measurement logs and if the server has attached the expected attestation data, and finally decodes/displays everything it receives. Additionally, the quote is written to `quote.bin`, while RTMR logs and report data are written to `quote.json`. 8 | 9 | Simply type `make` to build both the server and the client. `docker`, `openssl`, and [`aconcli`](../../doc/GettingStarted.md#building-aconcli) are required in the build process. 10 | 11 | Running the sample requires a TDX enabled platform. 12 | 13 | - The server must be started first, by 14 | 15 | ```sh 16 | TCP_PORT=5555 ATD=1 ATD_TCPFWD=8080:8085 ATD_KERNEL=/path/to/vmlinuz ATD_RD=/path/to/initrd.img aconcli run -ni -c:$TCP_PORT server.json 17 | ``` 18 | 19 | **Note**: `TCP_PORT` and `ATD` could be substituted by whatever deemed appropriate by the user. `ATD_TCPFWD` specifies TCP port forwarding rules and is in the form of `HOST_PORT:GUEST_PORT`. To make the same work, `GUEST_PORT` **must** be set to `8085`, while `HOST_PORT` should be set to the same value as passed to the client application on its command line. `ATD_KERNEL` and `ATD_RD` should be set to the file paths to the guest kernel and initrd image, respectively. 20 | 21 | - Given `ATD_TCPFWD=8080:8085` when starting the server, the client should be started by 22 | 23 | ```sh 24 | client/sampleclient 8080 25 | ``` 26 | 27 | **Note**: The `app` executable in the [client/](client/) directory is required for verifying the quote. It is built from the [quote verification sample code](https://github.com/intel-innersource/frameworks.security.confidential-computing.tee.dcap-trunk/tree/master/dcap_source/SampleCode/QuoteVerificationSample) of [DCAP](https://github.com/intel-innersource/frameworks.security.confidential-computing.tee.dcap-trunk) and is provided here for the users' convenience (so that DCAP wouldn't have to be built/installed for building this sample). -------------------------------------------------------------------------------- /scripts/start-qemu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2023 Intel Corporation 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | command -v qemu || { 6 | echo $0: \'qemu\' not found in '$PATH'! Please create symlink \ 7 | \'$HOME/bin/qemu\' pointing to the QEMU executable of your choice.>&2 8 | exit 1 9 | } 10 | 11 | host_fwd() { 12 | local fwd_rules= 13 | for v in $(echo $1|cut -d, -f1- --output-delimiter=' '); do 14 | fwd_rules=$fwd_rules,hostfwd=tcp::${v%%:*}-:${v##*:} 15 | done 16 | echo $fwd_rules 17 | } 18 | 19 | vdrives() { 20 | for v in $(echo $1|cut -d, -f1- --output-delimiter=' '); do 21 | echo "-drive if=virtio,file=$v,format=${v##*.}" 22 | done 23 | } 24 | 25 | initrd() { 26 | test "x$1" != "x-" && echo "-initrd $1" 27 | } 28 | 29 | eval exec qemu -nographic \ 30 | -accel kvm \ 31 | ${TD:+-object tdx-guest,id=tdx} \ 32 | ${TD:+-object memory-backend-memfd-private,id=ram1,size=${M:-2g}} \ 33 | -machine q35${TD:+,memory-backend=ram1,kernel_irqchip=split,confidential-guest-support=tdx} \ 34 | -cpu host,-kvm-steal-time,pmu=off \ 35 | ${VP:+-smp $VP} \ 36 | -m ${M:-2g} \ 37 | -nodefaults -vga none -no-hpet \ 38 | -nic user,model=virtio,ipv6=off,ipv4=on,hostname=${TD:+td-}${TD:-vm}$(host_fwd $TCPFWD) \ 39 | ${CID:+-device vhost-vsock-pci,guest-cid=$(test $CID -gt 2 && echo $CID || echo $$)} \ 40 | $(vdrives $DRV) \ 41 | -bios ${BIOS:-/usr/share/qemu/OVMF.fd} \ 42 | -chardev stdio,id=mux,mux=on,signal=off \ 43 | -device virtio-serial,max_ports=1 -device virtconsole,chardev=mux \ 44 | -serial chardev:mux \ 45 | -monitor chardev:mux \ 46 | -append \"ip=dhcp console=hvc0 earlyprintk=ttyS0 $KA\" \ 47 | $(initrd ${RD:-$(dirname $0)/initrd}) \ 48 | -kernel "${@:-$(dirname $0)/vmlinuz}" 49 | -------------------------------------------------------------------------------- /aconcli/cmd/invoke.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | 3 | package cmd 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "time" 9 | 10 | "aconcli/config" 11 | "aconcli/service" 12 | "github.com/spf13/cobra" 13 | ) 14 | 15 | var ( 16 | sizeToCapture uint64 17 | inputfile string 18 | ) 19 | 20 | var invokeCmd = &cobra.Command{ 21 | Use: "invoke custom_command [args]...", 22 | Short: "Invoke custom command in an ACON container", 23 | GroupID: "runtime", 24 | Long: ` 25 | Invoke a custom command in an existing ACON container within an ACON TD/VM. 26 | 27 | The ACON TD/VM must be specified by the '-c' flag while the ACON container must 28 | be specified by the '-e' flag. Use 'aconcli status' to list ACON TDs/VMs and 29 | ACON containers running in them. 30 | 31 | NOTE: A custom command is an executable file located in /lib/acon/entrypoint.d/ 32 | inside the ACON container's directory tree. Its file name must start with a 33 | capital letter. 34 | `, 35 | Args: cobra.MinimumNArgs(1), 36 | RunE: func(cmd *cobra.Command, args []string) error { 37 | return invoke(args) 38 | }, 39 | } 40 | 41 | func invoke(args []string) error { 42 | opts := []service.Opt{service.OptDialTLSContextInsecure(), 43 | service.OptTimeout(service.DefaultServiceTimeout + time.Duration(timeout)*time.Second)} 44 | if nologin { 45 | opts = append(opts, service.OptNoAuth()) 46 | } 47 | c, err := service.NewAconHttpConnWithOpts(vmConnTarget, opts...) 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, "Invoke: cannot connect to %s: %v\n", vmConnTarget, err) 50 | return err 51 | } 52 | 53 | stdout, stderr, err := c.Invoke(cid, args, timeout, env, inputfile, sizeToCapture) 54 | if err != nil { 55 | fmt.Fprintf(os.Stderr, "Invoke: cannot call 'invoke' service: %v\n", err) 56 | return err 57 | } 58 | 59 | if stdout != nil { 60 | fmt.Fprintln(os.Stdout, string(stdout)) 61 | } 62 | if stderr != nil { 63 | fmt.Fprintln(os.Stderr, string(stderr)) 64 | } 65 | return nil 66 | } 67 | 68 | func init() { 69 | rootCmd.AddCommand(invokeCmd) 70 | 71 | invokeCmd.Flags().StringVarP(&vmConnTarget, "connect", "c", "", 72 | "protocol/address of the ACON TD/VM") 73 | invokeCmd.MarkFlagRequired("connect") 74 | 75 | invokeCmd.Flags().Uint32VarP(&cid, "container", "e", 0, 76 | "the ACON container to execute the custom command") 77 | invokeCmd.MarkFlagRequired("container") 78 | 79 | invokeCmd.Flags().Uint64VarP(&timeout, "timeout", "t", 30, 80 | "capture up to this number of seconds of the command output") 81 | 82 | invokeCmd.Flags().Uint64VarP(&sizeToCapture, "size", "s", config.DefaultCapSize, 83 | "capture up to this number of bytes of the command output") 84 | 85 | invokeCmd.Flags().StringVarP(&inputfile, "input", "i", "", 86 | "optional file serving as stdin to the command") 87 | } 88 | -------------------------------------------------------------------------------- /acond/src/vsock_incoming.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use futures::{ready, Stream}; 5 | use std::{ 6 | os::unix::io::{AsRawFd, RawFd}, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | use tokio_vsock::VsockListener; 11 | 12 | mod vsock_stream { 13 | use super::*; 14 | use std::sync::Arc; 15 | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; 16 | use tokio_vsock::{VsockAddr, VsockStream as TokVsockStream}; 17 | use tonic::transport::server::Connected; 18 | 19 | #[derive(Debug)] 20 | pub struct VsockStream(pub TokVsockStream); 21 | 22 | impl Connected for VsockStream { 23 | type ConnectInfo = VsockConnectInfo; 24 | fn connect_info(&self) -> Self::ConnectInfo { 25 | VsockConnectInfo { 26 | peer_addr: self.0.peer_addr().ok().map(Arc::new), 27 | } 28 | } 29 | } 30 | 31 | #[derive(Clone, Debug)] 32 | pub struct VsockConnectInfo { 33 | pub peer_addr: Option>, 34 | } 35 | 36 | impl AsyncRead for VsockStream { 37 | fn poll_read( 38 | mut self: Pin<&mut Self>, 39 | cx: &mut Context<'_>, 40 | buf: &mut ReadBuf<'_>, 41 | ) -> Poll> { 42 | Pin::new(&mut self.0).poll_read(cx, buf) 43 | } 44 | } 45 | 46 | impl AsyncWrite for VsockStream { 47 | fn poll_write( 48 | mut self: Pin<&mut Self>, 49 | cx: &mut Context<'_>, 50 | buf: &[u8], 51 | ) -> Poll> { 52 | Pin::new(&mut self.0).poll_write(cx, buf) 53 | } 54 | 55 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 56 | Pin::new(&mut self.0).poll_flush(cx) 57 | } 58 | 59 | fn poll_shutdown( 60 | mut self: Pin<&mut Self>, 61 | cx: &mut Context<'_>, 62 | ) -> Poll> { 63 | Pin::new(&mut self.0).poll_shutdown(cx) 64 | } 65 | } 66 | } 67 | 68 | #[derive(Debug)] 69 | pub struct VsockIncoming { 70 | inner: VsockListener, 71 | } 72 | 73 | impl VsockIncoming { 74 | pub fn new(listener: VsockListener) -> Self { 75 | Self { inner: listener } 76 | } 77 | } 78 | 79 | impl Stream for VsockIncoming { 80 | type Item = std::io::Result; 81 | 82 | fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 83 | let (socket, _) = ready!(self.inner.poll_accept(cx))?; 84 | Poll::Ready(Some(Ok(vsock_stream::VsockStream(socket)))) 85 | } 86 | } 87 | 88 | impl AsRawFd for VsockIncoming { 89 | fn as_raw_fd(&self) -> RawFd { 90 | self.inner.as_raw_fd() 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /scripts/acon-startvm: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright © 2023 Intel Corporation 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | dump_env() { 6 | while test $# -gt 0; do 7 | eval "local readonly _V=\"\${$1:-(not set)}\"" 8 | printf '%s\t= %s\t# %s\n' $1 "$_V" "$2" 9 | shift 2 10 | done | 11 | column -t -s"$(printf '\t')" 12 | } 13 | 14 | host_fwd() { 15 | local fwd_rules= 16 | for v in $(echo $1|cut -d, -f1- --output-delimiter=' '); do 17 | fwd_rules=$fwd_rules,hostfwd=tcp::${v%%:*}-:${v##*:} 18 | done 19 | echo $fwd_rules 20 | } 21 | 22 | test -n "$ATD_CID" && test $ATD_CID -le 2 && ATD_CID=$$ 23 | 24 | CMD="${ATD_QEMU:=qemu-kvm} -nographic \ 25 | -name process=aconvm-$$ \ 26 | -accel kvm \ 27 | -smp ${ATD_NVP:=1} \ 28 | -m ${ATD_MEMSZ:=1g} \ 29 | ${ATD:+-object tdx-guest,id=tdx,quote-generation-service=${ATD_QGS:=vsock:2:4050}} \ 30 | ${ATD:+-object memory-backend-memfd-private,id=ram1,size=$ATD_MEMSZ}\ 31 | -machine q35${ATD:+,memory-backend=ram1,kernel_irqchip=split,confidential-guest-support=tdx} \ 32 | -cpu host,-kvm-steal-time,pmu=off \ 33 | -nodefaults -vga none -no-hpet \ 34 | -nic user,model=virtio,ipv6=off,ipv4=on,hostname=${ATD:+td-}${ATD:-vm}$(host_fwd $ATD_TCPFWD) \ 35 | ${ATD_CID:+-device vhost-vsock-pci,guest-cid=$ATD_CID} \ 36 | -bios ${ATD_BIOS:=/usr/share/qemu/OVMF.fd} \ 37 | -chardev stdio,id=mux,mux=on,signal=off \ 38 | -device virtio-serial,max_ports=1 -device virtconsole,chardev=mux \ 39 | -serial chardev:mux \ 40 | -monitor chardev:mux \ 41 | -append \"ip=dhcp console=hvc0 earlyprintk=ttyS0 ${ATD_CID:+acond.vsock_conn} $ATD_KPARAMS\" \ 42 | -initrd ${ATD_RD:=$(dirname $0)/initrd.img} \ 43 | -kernel \"${ATD_KERNEL:=$(dirname $0)/vmlinuz}\"" 44 | 45 | dump_env \ 46 | ATD "launch ${ATD:+td-}${ATD:-vm}" \ 47 | ATD_QGS "location of QGS" \ 48 | ATD_QEMU "QEMU executable" \ 49 | ATD_CID "VSOCK CID" \ 50 | ATD_MEMSZ "memory size" \ 51 | ATD_NVP "number of virtual processors" \ 52 | ATD_TCPFWD "TCP port forwarding rules" \ 53 | ATD_BIOS "BIOS image path" \ 54 | ATD_RD "initrd image path" \ 55 | ATD_KERNEL "kernel path" \ 56 | ATD_KPARAMS "additional kernel cmdline parameters" >&2 57 | 58 | eval exec $CMD 59 | -------------------------------------------------------------------------------- /aconcli/repo/bundle.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package repo 5 | 6 | import ( 7 | "encoding/hex" 8 | "encoding/json" 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | 13 | "aconcli/config" 14 | "aconcli/cryptoutil" 15 | ) 16 | 17 | type Bundle struct { 18 | path string 19 | } 20 | 21 | func NewBundle(path string) *Bundle { 22 | return &Bundle{path} 23 | } 24 | 25 | func (b *Bundle) Digest() ([]byte, error) { 26 | hashAlgo, err := cryptoutil.GetHashAlgoFromCert(b.Cert()) 27 | if err != nil { 28 | return nil, fmt.Errorf("cannot get bundle digest: %v", err) 29 | } 30 | m := b.Manifest() 31 | content, err := os.ReadFile(filepath.Clean(m)) 32 | if err != nil { 33 | return nil, fmt.Errorf("bundle digest: cannot read manifest file %s: %v", m, err) 34 | } 35 | content, err = canonicalJson(content) 36 | if err != nil { 37 | return nil, fmt.Errorf("bundle digest: cannot canonical manifest %s: %v", m, err) 38 | } 39 | digest, err := cryptoutil.BytesDigest(content, hashAlgo) 40 | if err != nil { 41 | return nil, fmt.Errorf("bundle digest: cannot get digest for manifest %s: %v", m, err) 42 | } 43 | return digest, nil 44 | } 45 | 46 | func (b *Bundle) SignerDigest() ([]byte, string, error) { 47 | return cryptoutil.GetCertDigest(b.Cert()) 48 | } 49 | 50 | func (b *Bundle) Manifest() string { 51 | return filepath.Join(b.path, config.ManifestFileName) 52 | } 53 | 54 | func (b *Bundle) Cert() string { 55 | return filepath.Join(b.path, config.CertFileName) 56 | } 57 | 58 | func (b *Bundle) Sig() string { 59 | return filepath.Join(b.path, config.SignatureFileName) 60 | } 61 | 62 | func (b *Bundle) Key() string { 63 | return filepath.Join(b.path, config.PrivKeyFileName) 64 | } 65 | 66 | func (b *Bundle) IsManifestUpdated() bool { 67 | digest, err := b.Digest() 68 | if err != nil { 69 | return false 70 | } 71 | dirname := filepath.Base(b.path) 72 | return dirname == hex.EncodeToString(digest) 73 | } 74 | 75 | func (b *Bundle) IsSignatureValid() bool { 76 | sig, err := os.ReadFile(b.Sig()) 77 | if err != nil { 78 | return false 79 | } 80 | content, err := os.ReadFile(b.Manifest()) 81 | if err != nil { 82 | return false 83 | } 84 | content, err = canonicalJson(content) 85 | if err != nil { 86 | return false 87 | } 88 | return cryptoutil.Verify(content, sig, b.Cert()) 89 | } 90 | 91 | func (b *Bundle) Remove() error { 92 | return os.RemoveAll(b.path) 93 | } 94 | 95 | func (b *Bundle) Layers() ([]string, error) { 96 | mfile := b.Manifest() 97 | content, err := os.ReadFile(filepath.Clean(mfile)) 98 | if err != nil { 99 | return nil, fmt.Errorf("get bundle layers: cannot read manifest %s: %v", mfile, err) 100 | } 101 | w := Workload{} 102 | if err := json.Unmarshal(content, &w); err != nil { 103 | return nil, fmt.Errorf("get bundle layers: cannot unmarshal manifest %s: %v", mfile, err) 104 | } 105 | return w.Layer, nil 106 | } 107 | -------------------------------------------------------------------------------- /acond/src/unix_incoming.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use futures::{ready, Stream}; 5 | use std::{ 6 | os::unix::io::{AsRawFd, RawFd}, 7 | pin::Pin, 8 | task::{Context, Poll}, 9 | }; 10 | use tokio::net::UnixListener; 11 | 12 | mod unix_stream { 13 | use super::*; 14 | use std::sync::Arc; 15 | use tokio::{ 16 | io::{AsyncRead, AsyncWrite, ReadBuf}, 17 | net::{ 18 | unix::{SocketAddr, UCred}, 19 | UnixStream as TokUnixStream, 20 | }, 21 | }; 22 | use tonic::transport::server::Connected; 23 | 24 | #[derive(Debug)] 25 | pub struct UnixStream(pub TokUnixStream); 26 | 27 | impl Connected for UnixStream { 28 | type ConnectInfo = UnixConnectInfo; 29 | fn connect_info(&self) -> Self::ConnectInfo { 30 | UnixConnectInfo { 31 | peer_addr: self.0.peer_addr().ok().map(Arc::new), 32 | peer_cred: self.0.peer_cred().ok(), 33 | } 34 | } 35 | } 36 | 37 | #[derive(Clone, Debug)] 38 | pub struct UnixConnectInfo { 39 | pub peer_addr: Option>, 40 | pub peer_cred: Option, 41 | } 42 | 43 | impl AsyncRead for UnixStream { 44 | fn poll_read( 45 | mut self: Pin<&mut Self>, 46 | cx: &mut Context<'_>, 47 | buf: &mut ReadBuf<'_>, 48 | ) -> Poll> { 49 | Pin::new(&mut self.0).poll_read(cx, buf) 50 | } 51 | } 52 | 53 | impl AsyncWrite for UnixStream { 54 | fn poll_write( 55 | mut self: Pin<&mut Self>, 56 | cx: &mut Context<'_>, 57 | buf: &[u8], 58 | ) -> Poll> { 59 | Pin::new(&mut self.0).poll_write(cx, buf) 60 | } 61 | 62 | fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 63 | Pin::new(&mut self.0).poll_flush(cx) 64 | } 65 | 66 | fn poll_shutdown( 67 | mut self: Pin<&mut Self>, 68 | cx: &mut Context<'_>, 69 | ) -> Poll> { 70 | Pin::new(&mut self.0).poll_shutdown(cx) 71 | } 72 | } 73 | } 74 | 75 | #[derive(Debug)] 76 | pub struct UnixIncoming { 77 | inner: UnixListener, 78 | } 79 | 80 | impl UnixIncoming { 81 | pub fn new(listener: UnixListener) -> Self { 82 | Self { inner: listener } 83 | } 84 | } 85 | 86 | impl Stream for UnixIncoming { 87 | type Item = std::io::Result; 88 | 89 | fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { 90 | let (socket, _) = ready!(self.inner.poll_accept(cx))?; 91 | Poll::Ready(Some(Ok(unix_stream::UnixStream(socket)))) 92 | } 93 | } 94 | 95 | impl AsRawFd for UnixIncoming { 96 | fn as_raw_fd(&self) -> RawFd { 97 | self.inner.as_raw_fd() 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /aconcli/README.md: -------------------------------------------------------------------------------- 1 | # Attested Container (ACON) Command Line Application 2 | 3 | ## Development Environment Setup 4 | 5 | To build and run `aconcli`, you need Golang, protocol buffer, and Go plugins for protocol compiler. 6 | 7 | - For Golang, please refer to the installation document [here](https://go.dev/doc/install). Version 1.20.6 and higher is recommended. 8 | 9 | - For protocol buffer and Go plugins, please refer to the installation guide listed [here](https://grpc.io/docs/languages/go/quickstart/). 10 | 11 | ## Build, install and clean the aconcli project 12 | 13 | To build `aconcli` project, change to the project's top level directory: 14 | 15 | ```sh 16 | go generate && go build # `-ldflags "-s -w"` can optionally be appended to strip symbols 17 | ``` 18 | 19 | To clean up: 20 | 21 | ```sh 22 | go clean 23 | ``` 24 | 25 | ## Running the aconcli tool 26 | 27 | To see available commands run: 28 | 29 | ``` 30 | aconcli -h 31 | ``` 32 | 33 | The general format of `aconcli` commands is: 34 | 35 | ``` 36 | aconcli [global_flags]... command [flags]... 37 | ``` 38 | 39 | Shell completion is supported. For example, to add completion to the current `bash`: 40 | 41 | ```sh 42 | . <(aconcli completion bash) 43 | ``` 44 | 45 | 46 | ## About the sample ACON-VM start sript 47 | 48 | With `aconcli`, a [default ACON-VM start script](acon-startvm) is provided to launch *ACON VM*s (*aVM* for short hereon). Environment variables are used by `aconcli run` (and potentially users as well) to convey parameters to customize the script's behavior. Users can supply their own start scripts but they must adhere to the semantics of the environment variables listed in the table below. 49 | 50 | |ENV VAR|Description 51 | |-|- 52 | |`ATD`|Set (to any non-nil string) to launch TD; unset to launch VM. `td-$ATD` will be used as the host name of the *aTD*. 53 | |`ATD_QEMU`|Executable name (or path) of *QEMU*, default `qemu-kvm`. 54 | |`ATD_CID`|VSOCK CID of the *aTD*.
  • Unset (default) - disable VSOCK support.
  • `$ATD_CID <= 2` - *PID* will be used as CID.
  • Otherwise - `$ATD_CID` will be used as CID as is. 55 | |`ATD_MEMSZ`|*aTD* memory size, default `1g` (1GB). 56 | |`ATD_NVP`|Number of virtual processors, default `1`. 57 | |`ATD_TCPFWD`|TCP forwarding rules - a comma (`,`) separated list of rules in the form of `[host_port:]guest_port`. For example,
    • `ATD_TCPFWD=1025` - Forward guest port `1025` to the same port (`1025`) on the host.
    • `ATD_TCPFWD=5022:22,1025` - Forward guest port `22` to host port `5022` and guest port `1025` to host port `1025`.
    `aconcli run` **appends** to `$ATD_TCPFWD` to forward `acond` port to the host. Users can set up forwarding rules for containers by setting this variable prior to invoking `aconcli`. 58 | |`ATD_BIOS`|Path to the virtual BIOS image, default `/usr/share/qemu/OVMF.fd`. 59 | |`ATD_RD`|Path to the initrd image, default `initrd.img` in the same directory as where the script resides. 60 | |`ATD_KERNEL`|Path to the guest kernel, default `vmlinuz` in the same directory as where the script resides. 61 | |`ATD_KPARAMS`|Additional kernel command line. `aconcli run` **appends** to `$ATD_KPARAMS` to pass parameters to `acond`. -------------------------------------------------------------------------------- /aconcli/cmd/remove.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "io/fs" 10 | "os" 11 | "path/filepath" 12 | "strings" 13 | 14 | "aconcli/repo" 15 | "github.com/spf13/cobra" 16 | ) 17 | 18 | var prune bool 19 | 20 | var rmCmd = &cobra.Command{ 21 | Use: "rm manifest...", 22 | Short: "Remove ACON images", 23 | GroupID: "image", 24 | Long: ` 25 | Remove one or more ACON images/manifests from their ACON image repos. 26 | 27 | Manifests can be specified either by paths or by hash digests, which can be 28 | obtained by 'aconcli ls'. 29 | 30 | 'aconcli rm' also removes certficate and signature files associated with the 31 | specified manifests, but it does NOT remove file system layers as they may be 32 | shared among multiple ACON images/manifests. 'aconcli prune' can be used to 33 | clean up unreferenced file system layers. 34 | `, 35 | RunE: func(cmd *cobra.Command, args []string) error { 36 | return removeManifest(args) 37 | }, 38 | } 39 | 40 | func findSingleMatch(dir, id string) (string, error) { 41 | pattern := filepath.Join(dir, id+"*") 42 | matches, err := filepath.Glob(pattern) 43 | if err != nil { 44 | return "", err 45 | } 46 | 47 | numMatch := len(matches) 48 | if numMatch == 1 { 49 | return matches[0], nil 50 | } 51 | 52 | return "", errors.New("No single match") 53 | } 54 | 55 | func isHashId(id string) bool { 56 | for _, hashAlgo := range supportHashAlgo { 57 | if strings.HasPrefix(id, hashAlgo+":") { 58 | return true 59 | } 60 | } 61 | return false 62 | } 63 | 64 | func removeManifest(ids []string) error { 65 | startingDir := "." 66 | if targetDir != "" { 67 | startingDir = targetDir 68 | } 69 | 70 | r, err := repo.FindRepo(startingDir) 71 | if err != nil { 72 | fmt.Fprintf(os.Stderr, "Remove Manifest: %v\n", err) 73 | return err 74 | } 75 | 76 | bundles, err := r.AllBundles() 77 | if err != nil { 78 | fmt.Fprintf(os.Stderr, "Remove Manifest: cannot get all manifests: %v\n", err) 79 | return err 80 | } 81 | 82 | var returnE error 83 | partialE := errors.New("work not fully completed") 84 | 85 | if prune { 86 | for _, b := range bundles { 87 | f := b.Manifest() 88 | fi, err := os.Lstat(f) 89 | if err != nil { 90 | continue 91 | } 92 | if fi.Mode()&fs.ModeSymlink != 0 && isSymlinkBroken(f) { 93 | if err := b.Remove(); err != nil { 94 | fmt.Fprintf(os.Stderr, "Remove Manifest: %s contains broken symlink, but failed to remove: %v\n", f, err) 95 | returnE = partialE 96 | } 97 | } 98 | } 99 | } 100 | 101 | for _, id := range ids { 102 | if err := r.RemoveBundle(id); err != nil { 103 | fmt.Fprintf(os.Stderr, "Remove Manifest: cannot remove manifest %s: %v\n", id, err) 104 | returnE = partialE 105 | } 106 | } 107 | 108 | return returnE 109 | } 110 | 111 | func init() { 112 | rootCmd.AddCommand(rmCmd) 113 | rmCmd.Flags().BoolVarP(&prune, "prune", "p", false, 114 | "also remove the manifest files whose symbolic link are currently broken") 115 | } 116 | -------------------------------------------------------------------------------- /sdk/include/acon_client.h: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | #ifndef _ACON_CLIENT_H_ 5 | #define _ACON_CLIENT_H_ 6 | #include 7 | 8 | #define SOCKET_PATH "/shared/acon.sock" 9 | 10 | typedef struct { 11 | uint8_t type; 12 | uint8_t subtype; 13 | uint8_t version; 14 | uint8_t _reserved; 15 | } report_type_t; 16 | 17 | typedef struct { 18 | uint8_t cpu_svn[16]; 19 | } cpu_svn_t; 20 | 21 | typedef struct { 22 | uint8_t hash[48]; 23 | } sha384_hash_t; 24 | 25 | typedef struct { 26 | report_type_t report_type; 27 | uint8_t reserved[12]; 28 | cpu_svn_t cpu_svn; 29 | sha384_hash_t tee_tcb_info_hash; 30 | sha384_hash_t tee_info_hash; 31 | uint8_t report_data[64]; 32 | uint8_t _reserved[32]; 33 | uint8_t mac[32]; 34 | } report_mac_t; 35 | 36 | typedef struct { 37 | uint8_t hash[239]; 38 | } tee_tcb_info_t; 39 | 40 | typedef struct { 41 | uint8_t attributes[8]; 42 | uint8_t xfam[8]; 43 | sha384_hash_t mrtd; 44 | sha384_hash_t mr_config_id; 45 | sha384_hash_t mr_owner; 46 | sha384_hash_t mr_owner_config; 47 | sha384_hash_t rtmr0; 48 | sha384_hash_t rtmr1; 49 | sha384_hash_t rtmr2; 50 | sha384_hash_t rtmr3; 51 | uint8_t _reserved[112]; 52 | } td_info_t; 53 | 54 | typedef struct { 55 | report_mac_t report_mac; 56 | tee_tcb_info_t tee_tcb_info; 57 | uint8_t _reserved[17]; 58 | td_info_t td_info; 59 | } td_report_t; 60 | 61 | typedef struct { 62 | int32_t command; // even = request; odd = response; negative = error 63 | uint32_t size; // size of the whole request/response 64 | } acon_message_hdr_t; 65 | 66 | typedef struct { 67 | acon_message_hdr_t header; // command = -Exxx, as defintion in Linux 68 | int32_t request; // original request code 69 | } acon_message_err_t; 70 | 71 | typedef struct { 72 | acon_message_hdr_t header; // command = 0 73 | uint32_t data_type; 74 | uint64_t nonce[2]; 75 | int32_t attest_data_type; // 0 = no data; 1 = binary; 2 = string; others = reserved 76 | } acon_get_report_req_t; 77 | 78 | typedef struct { 79 | acon_message_hdr_t header; // command = 1 80 | int32_t rtmr_log_offset; 81 | int32_t attestation_json_offset; 82 | int32_t data_offset; 83 | } acon_get_report_rsp_t; 84 | 85 | typedef struct { 86 | acon_message_hdr_t header; // command = 2 87 | int32_t attest_data_type; // Same definition as acon_get_report_req_t.attest_data_type 88 | } acon_set_attestation_data_req_t; 89 | 90 | typedef struct { 91 | acon_message_hdr_t header; // command = 3 92 | } acon_set_attestation_data_rsp_t; 93 | 94 | union acon_req { 95 | acon_message_hdr_t header; 96 | acon_get_report_req_t get_report_req; 97 | acon_set_attestation_data_req_t set_attestation_data_req; 98 | }; 99 | 100 | union acon_rsp { 101 | acon_message_hdr_t header; 102 | acon_message_err_t error; 103 | acon_get_report_rsp_t get_report_rsp; 104 | acon_set_attestation_data_rsp_t set_attestation_data_rsp; 105 | }; 106 | 107 | #endif 108 | -------------------------------------------------------------------------------- /aconcli/cryptoutil/hash_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cryptoutil 5 | 6 | import ( 7 | "crypto" 8 | "encoding/hex" 9 | "testing" 10 | ) 11 | 12 | type matchFileDigestTest struct { 13 | path, algo, want string 14 | } 15 | 16 | var matchFileDigestTests = []matchFileDigestTest{ 17 | {"../testdata/set1/cert.der", 18 | "sha384", 19 | "fccdc13f89825ca910902e7cf050f280399e106074efd9fb43bd2e8939c605225eb7157e0383f61883e7454fde2ba1c4", 20 | }, 21 | {"../testdata/set1/cert.der", 22 | "sha512", 23 | "7ccc0595461cb3be60c43bcc3759b06c6b5a9b0f4b911d1f4eab5faf9e04893a6742b37d48c8ee66659877b1894ddb66618d4fe4dae742e78464fd3ad66c199b", 24 | }, 25 | } 26 | 27 | func TestFileDigest(t *testing.T) { 28 | for _, test := range matchFileDigestTests { 29 | d, err := FileDigest(test.path, test.algo) 30 | if err != nil { 31 | t.Fatalf("FileDigest(%q, %q): %v", test.path, test.algo, err) 32 | } 33 | 34 | got := hex.EncodeToString(d) 35 | if got != test.want { 36 | t.Errorf("FileDigest(%q, %q) = %v, want %v", test.path, test.algo, got, test.want) 37 | } 38 | } 39 | } 40 | 41 | type matchGetCertDigestTest struct { 42 | path, wantAlgo, wantDigest string 43 | } 44 | 45 | var matchGetCertDigestTests = []matchGetCertDigestTest{ 46 | {"../testdata/set1/cert.der", 47 | "sha384", 48 | "fccdc13f89825ca910902e7cf050f280399e106074efd9fb43bd2e8939c605225eb7157e0383f61883e7454fde2ba1c4", 49 | }, 50 | {"../testdata/set2/cert.der", 51 | "sha384", 52 | "7ec795c3fe89687d4514a1e4f95b2421012fd0b877eba9d5d5f33a1314fe7e10a5b38b0890d890df59b716f55f41e2ff", 53 | }, 54 | } 55 | 56 | func TestGetCertDigest(t *testing.T) { 57 | for _, test := range matchGetCertDigestTests { 58 | d, a, err := GetCertDigest(test.path) 59 | if err != nil { 60 | t.Errorf("GetCertDigest(%q): %v", test.path, err) 61 | } 62 | 63 | if a != test.wantAlgo { 64 | t.Errorf("GetCertDigest(%q), got %q, want %q", test.path, a, test.wantAlgo) 65 | } 66 | 67 | got := hex.EncodeToString(d) 68 | if got != test.wantDigest { 69 | t.Errorf("GetCertDigest(%q), got %v, want %v", test.path, got, test.wantDigest) 70 | } 71 | } 72 | } 73 | 74 | type matchGetHashAlgoFromCertTest struct { 75 | path, want string 76 | } 77 | 78 | var matchGetHashAlgoFromCertTests = []matchGetHashAlgoFromCertTest{ 79 | {"../testdata/set1/cert.der", 80 | "sha384", 81 | }, 82 | {"../testdata/set2/cert.der", 83 | "sha384", 84 | }, 85 | } 86 | 87 | func TestGetHashAlgoFromCert(t *testing.T) { 88 | for _, test := range matchGetHashAlgoFromCertTests { 89 | h, err := GetHashAlgoFromCert(test.path) 90 | if err != nil { 91 | t.Errorf("GetHashAlgoFromCert(%q): %v", test.path, err) 92 | } 93 | 94 | if h != test.want { 95 | t.Errorf("GetHashAlgoFromCert(%q), got %q, want %q", test.path, h, test.want) 96 | } 97 | } 98 | } 99 | 100 | func Test_getHashAlgo(t *testing.T) { 101 | algo, err := getHashAlgo("sha384") 102 | if err != nil || algo != crypto.SHA384 { 103 | t.Errorf("getHashAlgo(\"sha384\")") 104 | } 105 | 106 | algo, err = getHashAlgo("sha512") 107 | if err != nil || algo != crypto.SHA512 { 108 | t.Errorf("getHashAlgo(\"sha512\")") 109 | } 110 | 111 | _, err = getHashAlgo("unknown") 112 | if err == nil { 113 | t.Errorf("getHashAlgo(\"unknown\")") 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /aconcli/cryptoutil/hash.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cryptoutil 5 | 6 | import ( 7 | "crypto" 8 | _ "crypto/sha512" 9 | "crypto/x509" 10 | "encoding/pem" 11 | "errors" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | ) 16 | 17 | type hashAlgoDescriptor struct { 18 | name string 19 | algo crypto.Hash 20 | } 21 | 22 | var ( 23 | hashAlgoSha384 = hashAlgoDescriptor{"sha384", crypto.SHA384} 24 | hashAlgoSha512 = hashAlgoDescriptor{"sha512", crypto.SHA512} 25 | supportedHash = []hashAlgoDescriptor{hashAlgoSha384, hashAlgoSha512} 26 | 27 | hashAlgoDescriptorMap = map[string]hashAlgoDescriptor{ 28 | "SHA384-RSA": hashAlgoSha384, 29 | "SHA512-RSA": hashAlgoSha512, 30 | "SHA384-RSAPSS": hashAlgoSha384, 31 | "SHA512-RSAPSS": hashAlgoSha512, 32 | "ECDSA-SHA384": hashAlgoSha384, 33 | "ECDSA-SHA512": hashAlgoSha512, 34 | "Ed25519": hashAlgoSha512, 35 | } 36 | ) 37 | 38 | func genericHash(data []byte, hash crypto.Hash) ([]byte, error) { 39 | if !hash.Available() { 40 | return nil, errors.New("cryptoutil: hash algo unavailable") 41 | } 42 | h := hash.New() 43 | h.Write(data) 44 | return h.Sum(nil), nil 45 | } 46 | 47 | func parseCertFile(certfile string) ([]byte, error) { 48 | dat, err := ioutil.ReadFile(filepath.Clean(certfile)) 49 | if err != nil { 50 | return nil, err 51 | } 52 | 53 | block, rest := pem.Decode(dat) 54 | if block == nil { 55 | return rest, nil 56 | } else if block.Type != "CERTIFICATE" { 57 | return nil, errors.New("cryptoutil: Certificate PEM block not found") 58 | } else { 59 | return block.Bytes, nil 60 | } 61 | } 62 | 63 | func GetHashAlgoFromCert(certfile string) (string, error) { 64 | derBytes, err := parseCertFile(certfile) 65 | if err != nil { 66 | return "", err 67 | } 68 | 69 | cert, err := x509.ParseCertificate(derBytes) 70 | if err != nil { 71 | return "", err 72 | } 73 | 74 | sigAlgoStr := cert.SignatureAlgorithm.String() 75 | hStr := hashAlgoDescriptorMap[sigAlgoStr].name 76 | return hStr, nil 77 | } 78 | 79 | func GetCertDigest(certfile string) ([]byte, string, error) { 80 | derBytes, err := parseCertFile(certfile) 81 | if err != nil { 82 | return nil, "", err 83 | } 84 | 85 | cert, err := x509.ParseCertificate(derBytes) 86 | if err != nil { 87 | return nil, "", err 88 | } 89 | 90 | sigAlgoStr := cert.SignatureAlgorithm.String() 91 | hStr := hashAlgoDescriptorMap[sigAlgoStr].name 92 | hAlgo := hashAlgoDescriptorMap[sigAlgoStr].algo 93 | 94 | digest, err := genericHash(derBytes, hAlgo) 95 | if err != nil { 96 | return nil, "", err 97 | } 98 | return digest, hStr, nil 99 | } 100 | 101 | func getHashAlgo(algo string) (hashAlgo crypto.Hash, err error) { 102 | for _, h := range supportedHash { 103 | if h.name == algo { 104 | hashAlgo = h.algo 105 | err = nil 106 | return 107 | } 108 | } 109 | err = errors.New("cryptoutil: Unknown hash algorithm") 110 | return 111 | } 112 | 113 | func FileDigest(path, algo string) ([]byte, error) { 114 | hashAlgo, err := getHashAlgo(algo) 115 | if err != nil { 116 | return nil, err 117 | } 118 | 119 | data, err := os.ReadFile(filepath.Clean(path)) 120 | if err != nil { 121 | return nil, err 122 | } 123 | return genericHash(data, hashAlgo) 124 | } 125 | 126 | func BytesDigest(data []byte, algo string) ([]byte, error) { 127 | hashAlgo, err := getHashAlgo(algo) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return genericHash(data, hashAlgo) 133 | } 134 | -------------------------------------------------------------------------------- /aconcli/cmd/shutdown.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "errors" 8 | "fmt" 9 | "os" 10 | 11 | "aconcli/config" 12 | "aconcli/service" 13 | "aconcli/vm" 14 | "github.com/spf13/cobra" 15 | ) 16 | 17 | var force bool 18 | 19 | var shutDownCmd = &cobra.Command{ 20 | Use: "shutdown ...", 21 | Short: "Shut down ACON TD/VM", 22 | GroupID: "runtime", 23 | Long: ` 24 | Shut down the specified ACON TD/VM. 25 | 26 | When there are running containers, 'aconcli shutdown' would try to stop them as 27 | if 'aconcli stop' were invoked on each of them, before shutting down the ACON 28 | TD/VM. 29 | 30 | The ACON TD/VM must be specified by the '-c' flag. Use 'aconcli status' to list 31 | ACON TDs/VMs and ACON containers running in them. 32 | `, 33 | RunE: func(cmd *cobra.Command, args []string) error { 34 | return removeVM(args) 35 | }, 36 | } 37 | 38 | func stopAcon(c service.AconClient, id uint32) error { 39 | // try invoke 'Stop' first 40 | _, _, err := c.Invoke(id, []string{"Stop"}, 5, nil, "", config.DefaultCapSize) 41 | if err == nil { 42 | return nil 43 | } 44 | fmt.Fprintf(os.Stderr, "cannot invoke 'Stop': %v\n", err) 45 | 46 | // invoke 'Kill' if 'Stop' fails 47 | _, _, err = c.Invoke(id, []string{"Kill", "-TERM", "1"}, 5, nil, "", config.DefaultCapSize) 48 | if err != nil { 49 | fmt.Fprintf(os.Stderr, "cannot invoke 'Kill': %v\n", err) 50 | } 51 | 52 | return err 53 | } 54 | 55 | func stopAconInVM(conn string, ids []uint32) error { 56 | opts := []service.Opt{service.OptDialTLSContextInsecure()} 57 | if nologin { 58 | opts = append(opts, service.OptNoAuth()) 59 | } 60 | c, err := service.NewAconHttpConnWithOpts(conn, opts...) 61 | if err != nil { 62 | return fmt.Errorf("cannot connect to %s: %v\n", conn, err) 63 | } 64 | 65 | partialComplete := false 66 | for _, id := range ids { 67 | err := stopAcon(c, id) 68 | if err != nil { 69 | fmt.Fprintf(os.Stderr, "cannot stop ACON container %v: %v\n", id, err) 70 | partialComplete = true 71 | } 72 | } 73 | if partialComplete { 74 | return errors.New("some ACON containers are not stopped") 75 | } else { 76 | return nil 77 | } 78 | } 79 | 80 | func removeVM(conns []string) error { 81 | for _, conn := range conns { 82 | // 'force', terminate the VM 83 | if force { 84 | pid, err := vm.GetPid(conn) 85 | if err != nil { 86 | fmt.Fprintf(os.Stderr, "Shutdown: cannot get VM pid for %s: %v\n", conn, err) 87 | continue 88 | } 89 | if err := vm.DestroyVM(pid); err != nil { 90 | fmt.Fprintf(os.Stderr, "Shutdown: cannot destroy VM %d: %v\n", pid, err) 91 | } 92 | } else { 93 | // non 'force', stop all the ACON containers 94 | states, err := getAllStatus(conn) 95 | if err != nil { 96 | fmt.Fprintf(os.Stderr, "Shutdown: cannot get ACON containers status from %s: %v\n", conn, err) 97 | continue 98 | } 99 | 100 | ids := make([]uint32, len(states)) 101 | for i, s := range states { 102 | ids[i] = s.ContainerId 103 | } 104 | 105 | if err := stopAconInVM(conn, ids); err != nil { 106 | fmt.Fprintf(os.Stderr, "Shutdown: cannot stop ACON containers in %s: %v\n", conn, err) 107 | continue 108 | } 109 | } 110 | } 111 | return nil 112 | } 113 | 114 | func init() { 115 | rootCmd.AddCommand(shutDownCmd) 116 | shutDownCmd.Flags().BoolVarP(&force, "force", "f", false, 117 | "force terminating the virtual machines, i.e. no matter whether Shutdown/Kill command works") 118 | } 119 | -------------------------------------------------------------------------------- /aconcli/cmd/status.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | "os" 10 | "path/filepath" 11 | "strings" 12 | 13 | "aconcli/config" 14 | "aconcli/repo" 15 | "aconcli/service" 16 | "aconcli/vm" 17 | 18 | "github.com/spf13/cobra" 19 | ) 20 | 21 | var statusCmd = &cobra.Command{ 22 | Use: "status", 23 | Short: "Show status of ACON TDs/VMs and containers", 24 | GroupID: "runtime", 25 | Long: ` 26 | Show status of all ACON TDs/VMs on the local platform and containers running in 27 | them. 28 | `, 29 | Args: cobra.NoArgs, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | return showStatus() 32 | }, 33 | } 34 | 35 | func getAllStatus(target string) ([]service.AconStatus, error) { 36 | opts := []service.Opt{service.OptDialTLSContextInsecure()} 37 | if nologin { 38 | opts = append(opts, service.OptNoAuth()) 39 | } 40 | c, err := service.NewAconHttpConnWithOpts(target, opts...) 41 | if err != nil { 42 | return nil, err 43 | } 44 | 45 | r, err := c.Inspect(0) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | return r, nil 51 | } 52 | 53 | func printStatus(vmName string, aconStatus []service.AconStatus) { 54 | fmt.Printf("\nTotal %d ACON container(s) in VM %s\n", len(aconStatus), vmName) 55 | for i, t := range aconStatus { 56 | state := "Terminated" 57 | if t.State != 0 { 58 | state = string(t.State) 59 | } 60 | fmt.Printf("[%d]\tInstance ID:\t\t%d\n", i, t.ContainerId) 61 | fmt.Printf("\tInstance state:\t\t%s\n", state) 62 | fmt.Printf("\tInstance wstatus:\t%d\n", t.Wstatus) 63 | fmt.Printf("\tInstance bundle ID:\t%v\n", convertImageId(t.ImageId)) 64 | fmt.Printf("\tInstance exe path:\t%v\n", t.ExePath) 65 | fmt.Println() 66 | } 67 | } 68 | 69 | func convertImageId(bundleId string) string { 70 | startingDir := "." 71 | if targetDir != "" { 72 | startingDir = targetDir 73 | } 74 | r, err := repo.FindRepo(startingDir) 75 | if err != nil { 76 | fmt.Fprintf(os.Stderr, "convert bundle id: %v\n", err) 77 | return bundleId 78 | } 79 | 80 | bundleIdSlice := strings.Split(bundleId, "/") 81 | hashAlgo := bundleIdSlice[0] 82 | signerDigest := bundleIdSlice[1] 83 | manifestDigest := bundleIdSlice[2] 84 | 85 | bundle, err := r.FindBundle(manifestDigest) 86 | if err != nil { 87 | fmt.Fprintf(os.Stderr, "find bundle: %v\n", err) 88 | return bundleId 89 | } 90 | 91 | manifest := bundle.Manifest() 92 | content, err := os.ReadFile(filepath.Clean(manifest)) 93 | if err != nil { 94 | return bundleId 95 | } 96 | 97 | w := repo.Workload{} 98 | if err := json.Unmarshal(content, &w); err != nil { 99 | fmt.Fprintf(os.Stderr, "cannot unmarshal %s: %v", manifest, err) 100 | return bundleId 101 | } 102 | 103 | productName := w.Alias.Self 104 | if productName == nil { 105 | return filepath.Join(hashAlgo, signerDigest[:config.ShortHashLen], manifestDigest[:config.ShortHashLen]) 106 | } else { 107 | return productName["."][0] 108 | } 109 | } 110 | 111 | func showStatus() error { 112 | vmPids, conns, err := vm.GetAllVM(config.AconVmPrefix) 113 | if err != nil { 114 | fmt.Fprintf(os.Stderr, "Show Status: cannot fetch VM information: %v\n", err) 115 | return err 116 | } 117 | fmt.Fprintf(os.Stdout, "\nTotal Runing ACON VMs: %v\n", vmPids) 118 | 119 | type item struct { 120 | conn string 121 | status []service.AconStatus 122 | err error 123 | } 124 | 125 | ch := make(chan item, len(conns)) 126 | // get instaces status in each VM 127 | for i, conn := range conns { 128 | fmt.Fprintf(os.Stdout, "Inspect Virtual Machine: %s\n", vmPids[i]) 129 | go func(c string) { 130 | var it item 131 | it.conn = c 132 | it.status, it.err = getAllStatus(c) 133 | ch <- it 134 | }(conn) 135 | } 136 | 137 | for range conns { 138 | it := <-ch 139 | if it.err != nil { 140 | fmt.Fprintf(os.Stderr, "Show Status: cannot fetch status from %s: %v\n", it.conn, it.err) 141 | continue 142 | } 143 | printStatus(it.conn, it.status) 144 | } 145 | return nil 146 | } 147 | 148 | func init() { 149 | rootCmd.AddCommand(statusCmd) 150 | } 151 | -------------------------------------------------------------------------------- /aconcli/repo/repo_bundle_test.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package repo 5 | 6 | import ( 7 | "os" 8 | "path/filepath" 9 | "strings" 10 | "testing" 11 | 12 | "aconcli/config" 13 | ) 14 | 15 | const ( 16 | repoTestPath = "../testdata/test_repo" 17 | repoPath = repoTestPath + "/" + config.RepoDirName 18 | ) 19 | 20 | var testingR *Repo 21 | 22 | func TestMain(m *testing.M) { 23 | 24 | if err := os.Mkdir(repoPath, 0750); err != nil { 25 | os.Exit(-1) 26 | } 27 | r, err := FindRepo(repoTestPath) 28 | if err != nil { 29 | os.Exit(-1) 30 | } 31 | testingR = r 32 | exitVal := m.Run() 33 | os.RemoveAll(repoPath) 34 | os.Exit(exitVal) 35 | } 36 | 37 | func TestFindRepo(t *testing.T) { 38 | if !strings.HasSuffix(testingR.path, "testdata/test_repo/"+config.RepoDirName) { 39 | t.Errorf("FindRepo: got %s", testingR.path) 40 | } 41 | } 42 | 43 | func TestCommitBlob(t *testing.T) { 44 | repoLayers := []struct { 45 | name string 46 | digestSha256 string 47 | digestSha384 string 48 | }{ 49 | { 50 | "acon_image_layer0", 51 | "26a89af864a0f616a1ad8ed92788933582be59caa06c34d40e1b24cfba1d7e1d", 52 | "de9d9395ea0a434af3ffd9ae13c9ecf0b8a209881a7a6785b5f5427c0b613bab5c8dbb37bb577ae484cf9d90b13ef5f3", 53 | }, 54 | { 55 | "acon_image_layer1", 56 | "e73175481c5741e0fedf323ec2b66bcbb2cbc10a6be0f3eca03e6a8e2b4a1970", 57 | "86a35cf623699eaa741712b80d342a7aa350ceb445aaf7c550199737635619e25038027a72db005672ba30f47ea39204", 58 | }, 59 | { 60 | "acon_image_layer3", 61 | "7933ca5b8fadb74a08c558b3c560ce2690ee767df7ac6fa8aedfb3552108d6ea", 62 | "badd0ba0ce62b587b507ad0d4451040f7c7d3bbbb81a572c6723f8cf8b7b081ea81b6b7c81cbfd31fa934ba77c809365", 63 | }, 64 | } 65 | blobs := make([][]byte, len(repoLayers)) 66 | diffIds := make([]string, len(repoLayers)) 67 | 68 | for i, layer := range repoLayers { 69 | blobs[i], _ = os.ReadFile(filepath.Join(repoTestPath, layer.name)) 70 | diffIds[i] = "sha256:" + layer.digestSha256 71 | } 72 | err := testingR.CommitBlob(blobs, diffIds) 73 | if err != nil { 74 | t.Fatal(err) 75 | } 76 | 77 | for _, layer := range repoLayers { 78 | primaryDigest, err := testingR.PrimaryDigest("sha256:" + layer.digestSha256) 79 | if err != nil { 80 | t.Fatal(err) 81 | } 82 | want := "sha384/" + layer.digestSha384 83 | if primaryDigest != want { 84 | t.Errorf("repo.PrimaryDigest, want: %s, got: %s", want, primaryDigest) 85 | } 86 | } 87 | } 88 | 89 | func TestCommitManifest(t *testing.T) { 90 | err := testingR.CommitManifest(filepath.Join(repoTestPath, config.ManifestFileName), 91 | "../testdata/set2/cert.der", 92 | "../testdata/set2/priv.pem") 93 | if err != nil { 94 | t.Fatal(err) 95 | } 96 | } 97 | 98 | func TestFindBundle(t *testing.T) { 99 | bundleByName, err := testingR.FindBundle(filepath.Join(repoTestPath, config.ManifestFileName)) 100 | if err != nil { 101 | t.Fatal(err) 102 | } 103 | 104 | bundleByHash, err := testingR.FindBundle("986f928e2e8c") 105 | if err != nil { 106 | t.Fatal(err) 107 | } 108 | 109 | if bundleByName.path != bundleByHash.path { 110 | t.Fatal(err) 111 | } 112 | } 113 | 114 | func TestBundle(t *testing.T) { 115 | bundle, err := testingR.FindBundle(filepath.Join(repoTestPath, config.ManifestFileName)) 116 | if err != nil { 117 | t.Fatal(err) 118 | } 119 | 120 | if !bundle.IsManifestUpdated() { 121 | t.Error(err) 122 | } 123 | 124 | if !bundle.IsSignatureValid() { 125 | t.Error(err) 126 | } 127 | 128 | layers, err := bundle.Layers() 129 | if err != nil { 130 | t.Fatal(err) 131 | } 132 | 133 | want := []string{ 134 | "sha384/de9d9395ea0a434af3ffd9ae13c9ecf0b8a209881a7a6785b5f5427c0b613bab5c8dbb37bb577ae484cf9d90b13ef5f3", 135 | "sha384/86a35cf623699eaa741712b80d342a7aa350ceb445aaf7c550199737635619e25038027a72db005672ba30f47ea39204", 136 | "sha384/badd0ba0ce62b587b507ad0d4451040f7c7d3bbbb81a572c6723f8cf8b7b081ea81b6b7c81cbfd31fa934ba77c809365", 137 | } 138 | 139 | if len(layers) != len(want) { 140 | t.Fatalf("bundle.Layers: want %v, got %v", want, layers) 141 | } 142 | 143 | for i := range want { 144 | if layers[i] != want[i] { 145 | t.Errorf("bundle.Layers: layer %d, want %v, got %v", i, want[i], layers[i]) 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /aconcli/fileutil/fileutil.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package fileutil 5 | 6 | import ( 7 | "archive/tar" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "io" 12 | "io/ioutil" 13 | "os" 14 | "path/filepath" 15 | "strings" 16 | ) 17 | 18 | type m struct { 19 | Layers []string `json:"Layers"` 20 | } 21 | 22 | func UntarBlob(r io.Reader) ([]string, map[string][]byte, error) { 23 | tmpData := make(map[string][]byte) 24 | layerData := make(map[string][]byte) 25 | var layerString []string 26 | tr := tar.NewReader(r) 27 | for { 28 | header, err := tr.Next() 29 | switch { 30 | case err == io.EOF: 31 | return layerString, layerData, nil 32 | 33 | case err != nil: 34 | return nil, nil, err 35 | 36 | case header == nil: 37 | continue 38 | } 39 | 40 | switch header.Typeflag { 41 | case tar.TypeReg: 42 | if strings.HasSuffix(header.Name, ".tar") || 43 | strings.HasPrefix(header.Name, "blobs") { 44 | data, err := ioutil.ReadAll(tr) 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | tmpData[header.Name] = data 49 | } 50 | if header.Name == "manifest.json" { 51 | data, err := ioutil.ReadAll(tr) 52 | if err != nil { 53 | return nil, nil, err 54 | } 55 | var manifest []m 56 | err = json.Unmarshal(data, &manifest) 57 | if err != nil { 58 | return nil, nil, err 59 | } 60 | layerString = manifest[0].Layers 61 | for _, layer := range layerString { 62 | layerData[layer] = tmpData[layer] 63 | } 64 | } 65 | } 66 | } 67 | } 68 | 69 | func Tar(tarFilePath string, filePaths []string, fileNameFilter func(string) string) error { 70 | tarFile, err := os.Create(filepath.Clean(tarFilePath)) 71 | if err != nil { 72 | return fmt.Errorf("fail to create tar file %v, err %v", tarFilePath, err) 73 | 74 | } 75 | defer tarFile.Close() 76 | 77 | tarWriter := tar.NewWriter(tarFile) 78 | defer tarWriter.Close() 79 | 80 | for _, filePath := range filePaths { 81 | err := addFileToTar(filePath, tarWriter, fileNameFilter) 82 | if err != nil { 83 | return fmt.Errorf("fail to add file %v, err %v", filePath, err) 84 | } 85 | } 86 | 87 | return nil 88 | } 89 | 90 | func addFileToTar(filePath string, tarWriter *tar.Writer, nameFilter func(string) string) error { 91 | file, err := os.Open(filepath.Clean(filePath)) 92 | if err != nil { 93 | return fmt.Errorf("fail to open file %v, err %v", filePath, err) 94 | } 95 | defer file.Close() 96 | 97 | stat, err := file.Stat() 98 | if err != nil { 99 | return fmt.Errorf("fail to stat file %v, err %v", filePath, err) 100 | } 101 | 102 | if nameFilter != nil { 103 | filePath = nameFilter(filePath) 104 | } 105 | 106 | header := &tar.Header{ 107 | Name: filePath, 108 | Size: stat.Size(), 109 | Mode: int64(stat.Mode()), 110 | ModTime: stat.ModTime(), 111 | } 112 | 113 | err = tarWriter.WriteHeader(header) 114 | if err != nil { 115 | return fmt.Errorf("fail to write header for file %v, err %v", filePath, err) 116 | } 117 | 118 | _, err = io.Copy(tarWriter, file) 119 | if err != nil { 120 | return fmt.Errorf("fail to copy file %v into tarball, err %v", filePath, err) 121 | } 122 | 123 | return nil 124 | } 125 | 126 | func Untar(dst string, r io.Reader) error { 127 | tr := tar.NewReader(r) 128 | for { 129 | header, err := tr.Next() 130 | switch { 131 | case err == io.EOF: 132 | return nil 133 | case err != nil: 134 | return err 135 | 136 | case header == nil: 137 | continue 138 | } 139 | 140 | target := filepath.Join(dst, header.Name) 141 | if !strings.HasPrefix(filepath.Clean(target), dst) { 142 | return fmt.Errorf("invalid path: %s", target) 143 | } 144 | switch header.Typeflag { 145 | case tar.TypeDir: 146 | if _, err := os.Stat(target); err == os.ErrNotExist { 147 | if err := os.MkdirAll(target, 0750); err != nil { 148 | return err 149 | } 150 | } 151 | case tar.TypeReg: 152 | if _, err := os.Stat(target); err == nil { 153 | continue 154 | } 155 | dir := filepath.Dir(target) 156 | if _, err := os.Stat(dir); errors.Is(err, os.ErrNotExist) { 157 | if err := os.MkdirAll(dir, 0750); err != nil { 158 | return err 159 | } 160 | } 161 | f, err := os.OpenFile(filepath.Clean(target), os.O_CREATE|os.O_RDWR, 0600) 162 | if err != nil { 163 | return err 164 | } 165 | if _, err := io.CopyN(f, tr, header.Size); err != nil { 166 | return err 167 | } 168 | f.Close() 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /aconcli/cmd/ls.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "encoding/hex" 8 | "errors" 9 | "fmt" 10 | "io/fs" 11 | "os" 12 | "path/filepath" 13 | "strconv" 14 | 15 | "aconcli/config" 16 | "aconcli/repo" 17 | "github.com/spf13/cobra" 18 | ) 19 | 20 | var notrunc bool 21 | 22 | var lsCmd = &cobra.Command{ 23 | Use: "ls", 24 | Short: "List ACON images", 25 | GroupID: "image", 26 | Long: ` 27 | List ACON images in the current acon image repo, whose path is determined by 28 | the current working directory. 29 | `, 30 | RunE: func(cmd *cobra.Command, args []string) error { 31 | return lsManifest() 32 | }, 33 | } 34 | 35 | var manifestStatusTitile = []string{ 36 | "NAME", 37 | "STATUS", 38 | "VALID", 39 | "DIGEST", 40 | "SIGNER DIGEST", 41 | "KEY STATUS", 42 | "SIG VALID"} 43 | 44 | func isSymlinkBroken(path string) bool { 45 | target, err := filepath.EvalSymlinks(path) 46 | if err != nil { 47 | return true 48 | } 49 | _, err = os.Stat(target) 50 | if errors.Is(err, fs.ErrNotExist) { 51 | return true 52 | } 53 | return false 54 | } 55 | 56 | func filetype(path string) (string, string) { 57 | fi, err := os.Lstat(path) 58 | if err != nil { 59 | return "invalid", path 60 | } 61 | 62 | switch mode := fi.Mode(); { 63 | case mode.IsRegular(): 64 | return "regular", path 65 | 66 | case mode&fs.ModeSymlink != 0: 67 | target, err := filepath.EvalSymlinks(path) 68 | if err != nil { 69 | return "broken symlink", path 70 | } else if isSymlinkBroken(path) { 71 | return "broken symlink", target 72 | } else { 73 | return "symlink", target 74 | } 75 | default: 76 | return "invalid", path 77 | } 78 | } 79 | 80 | func bundleStatus(b *repo.Bundle) []string { 81 | st := make([]string, len(manifestStatusTitile)) 82 | 83 | mpath := b.Manifest() 84 | manifestType, manifestPath := filetype(mpath) 85 | currAbs, _ := filepath.Abs(".") 86 | rel, _ := filepath.Rel(currAbs, manifestPath) 87 | st[0] = rel 88 | st[1] = manifestType 89 | st[2] = strconv.FormatBool(b.IsManifestUpdated()) 90 | digest, err := b.Digest() 91 | if err != nil { 92 | st[3] = "INVALID" 93 | } else if notrunc { 94 | st[3] = hex.EncodeToString(digest) 95 | } else { 96 | st[3] = hex.EncodeToString(digest)[:config.ShortHashLen] 97 | } 98 | digest, _, err = b.SignerDigest() 99 | if err != nil { 100 | st[4] = "INVALID" 101 | } else if notrunc { 102 | st[4] = hex.EncodeToString(digest) 103 | } else { 104 | st[4] = hex.EncodeToString(digest)[:config.ShortHashLen] 105 | } 106 | kpath := b.Key() 107 | ktype, _ := filetype(kpath) 108 | st[5] = ktype 109 | st[6] = strconv.FormatBool(b.IsSignatureValid()) 110 | return st 111 | } 112 | 113 | func getFormat(mtuples [][]string) []string { 114 | lens := make([]int, len(mtuples[0])) 115 | for _, mtuple := range mtuples { 116 | for i, e := range mtuple { 117 | length := len(e) 118 | if length > lens[i] { 119 | lens[i] = length 120 | } 121 | } 122 | } 123 | extra := 4 124 | var format []string 125 | for _, length := range lens { 126 | // something like %-9v, left-justifies the string 127 | f := fmt.Sprintf("%%-%dv", length+extra) 128 | format = append(format, f) 129 | } 130 | return format 131 | } 132 | 133 | func printManifestStatus(format []string, mtuple []string) { 134 | for i := range format { 135 | fmt.Fprintf(os.Stdout, format[i], mtuple[i]) 136 | } 137 | fmt.Fprintf(os.Stdout, "\n") 138 | } 139 | 140 | func printManifests(bundles []*repo.Bundle) { 141 | allBundleStatus := [][]string{manifestStatusTitile} 142 | for _, b := range bundles { 143 | status := bundleStatus(b) 144 | allBundleStatus = append(allBundleStatus, status) 145 | } 146 | format := getFormat(allBundleStatus) 147 | for _, s := range allBundleStatus { 148 | printManifestStatus(format, s) 149 | } 150 | } 151 | 152 | func lsManifest() error { 153 | startingDir := "." 154 | if targetDir != "" { 155 | startingDir = targetDir 156 | } 157 | r, err := repo.FindRepo(startingDir) 158 | if err != nil { 159 | fmt.Fprintf(os.Stderr, "List Manifest: %v\n", err) 160 | return err 161 | } 162 | bundles, err := r.AllBundles() 163 | if err != nil { 164 | fmt.Fprintf(os.Stderr, "List Manifest: cannot get manifests to show: %v\n", err) 165 | return err 166 | } 167 | printManifests(bundles) 168 | return nil 169 | } 170 | 171 | func init() { 172 | rootCmd.AddCommand(lsCmd) 173 | lsCmd.Flags().BoolVar(¬runc, "no-trunc", false, "Don't truncate output for hash digest") 174 | } 175 | -------------------------------------------------------------------------------- /aconcli/vm/vm.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package vm 5 | 6 | import ( 7 | "fmt" 8 | "os" 9 | "os/exec" 10 | "path/filepath" 11 | "strconv" 12 | "strings" 13 | "syscall" 14 | "time" 15 | 16 | "aconcli/config" 17 | 18 | "github.com/prometheus/procfs" 19 | ) 20 | 21 | const ( 22 | procDir = "/proc" 23 | commfile = "comm" 24 | aconStartFile = "acon-startvm" 25 | ) 26 | 27 | func exePath() (string, error) { 28 | ex, err := os.Executable() 29 | if err != nil { 30 | return "", fmt.Errorf("find aconcli executable failed: %v", err) 31 | } 32 | // If a symlink was used to start the process, depending on the operating system, 33 | // the result might be the symlink or the path it pointed to. If a stable result 34 | // is needed, path/filepath.EvalSymlinks might help. 35 | return filepath.EvalSymlinks(ex) 36 | } 37 | 38 | func getQemuStartFile(startfile string) (string, error) { 39 | if len(startfile) > 0 { 40 | return startfile, nil 41 | } 42 | exepath, err := exePath() 43 | if err != nil { 44 | return "", err 45 | } 46 | return filepath.Join(filepath.Dir(exepath), aconStartFile), nil 47 | } 48 | 49 | func StartVM(startfile string, foreground bool, env []string) (*exec.Cmd, error) { 50 | qemuStartFile, err := getQemuStartFile(startfile) 51 | if err != nil { 52 | return nil, fmt.Errorf("startVM failed: %v", err) 53 | } 54 | 55 | cmd := exec.Cmd{Path: qemuStartFile, Env: env} 56 | 57 | if foreground { 58 | cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr 59 | cmd.SysProcAttr = &syscall.SysProcAttr{ 60 | Foreground: true, 61 | } 62 | } 63 | if err = cmd.Start(); err != nil { 64 | return nil, fmt.Errorf("startVM failed: %v", err) 65 | } 66 | time.Sleep(time.Second) 67 | return &cmd, nil 68 | } 69 | 70 | func DestroyVM(pid int) error { 71 | process, err := os.FindProcess(pid) 72 | if err != nil { 73 | return err 74 | } 75 | return process.Signal(syscall.SIGTERM) 76 | } 77 | 78 | func GetAllVM(vmNamePrefix string) ([]string, []string, error) { 79 | matches, err := filepath.Glob(procDir + "/[0-9]*[0-9]") 80 | if err != nil { 81 | return nil, nil, fmt.Errorf("get all vm: cannot glob pid directory: %v", err) 82 | } 83 | 84 | var pids []string 85 | var conns []string 86 | for _, match := range matches { 87 | finfo, err := os.Stat(match) 88 | if err != nil || !finfo.IsDir() { 89 | continue 90 | } 91 | comm, err := evalCommFile(filepath.Join(match, commfile)) 92 | if err != nil { 93 | fmt.Fprintln(os.Stderr, err) 94 | continue 95 | } 96 | if !strings.HasPrefix(comm, vmNamePrefix) { 97 | continue 98 | } 99 | pid := filepath.Base(match) 100 | if conn, err := getConnTarget(pid); err == nil { 101 | pids = append(pids, pid) 102 | conns = append(conns, conn) 103 | } 104 | } 105 | return pids, conns, nil 106 | } 107 | 108 | func GetPid(connTarget string) (int, error) { 109 | vmPids, conns, err := GetAllVM(config.AconVmPrefix) 110 | if err != nil { 111 | return -1, fmt.Errorf("get pid for (%s): %v", connTarget, err) 112 | } 113 | 114 | for i, conn := range conns { 115 | if conn == connTarget { 116 | return strconv.Atoi(vmPids[i]) 117 | } 118 | } 119 | return -1, fmt.Errorf("get pid for (%s): cannot get matching pid", connTarget) 120 | } 121 | 122 | func evalCommFile(f string) (string, error) { 123 | c, err := os.ReadFile(filepath.Clean(f)) 124 | if err != nil { 125 | return "", err 126 | } 127 | name := strings.TrimSpace(string(c)) 128 | return name, nil 129 | } 130 | 131 | func getConnTarget(pid string) (string, error) { 132 | targetPid, err := strconv.Atoi(pid) 133 | if err != nil { 134 | return "", fmt.Errorf("cannot convert pid for %s", pid) 135 | } 136 | pfs, err := procfs.NewDefaultFS() 137 | if err != nil { 138 | return "", fmt.Errorf("cannot new default FS, err %v", err) 139 | } 140 | 141 | p, err := pfs.Proc(targetPid) 142 | if err != nil { 143 | return "", fmt.Errorf("cannot Proc: %v", err) 144 | } 145 | 146 | environ, err := p.Environ() 147 | if err != nil { 148 | return "", fmt.Errorf("cannot get env var for pid %s: %v", pid, err) 149 | } 150 | 151 | var connTarget string 152 | for _, ev := range environ { 153 | if strings.HasPrefix(ev, config.AconVmEnvTag) { 154 | connTarget = ev 155 | break 156 | } 157 | } 158 | 159 | if connTarget == "" { 160 | return "", fmt.Errorf("cannot find vm connection target") 161 | } 162 | 163 | _, v, found := strings.Cut(connTarget, "=") 164 | if !found { 165 | return "", fmt.Errorf("getConnTarget: malformed tdvm connection ENV VAR %v", connTarget) 166 | } 167 | 168 | return v, nil 169 | } 170 | -------------------------------------------------------------------------------- /aconcli/cmd/generate.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "context" 8 | "encoding/json" 9 | "fmt" 10 | "os" 11 | "path/filepath" 12 | 13 | "github.com/docker/docker/client" 14 | "github.com/spf13/cobra" 15 | 16 | "aconcli/fileutil" 17 | "aconcli/repo" 18 | ) 19 | 20 | var imageId string 21 | 22 | var generateCmd = &cobra.Command{ 23 | Use: "generate ", 24 | Short: "Generate a manifest file and commit file system layers to ACON repository", 25 | GroupID: "image", 26 | Long: ` 27 | Generate a manifest file in JSON format and commit to ACON repository 28 | the file system layers from specified Docker image. The output manifest 29 | file can be further edited manually and can be signed or re-signed using 30 | the 'aconcli sign' sub-command`, 31 | RunE: func(cmd *cobra.Command, args []string) error { 32 | return generateManifest(args) 33 | }, 34 | } 35 | 36 | func generateManifest(args []string) error { 37 | if len(args) > 0 { 38 | manifestFile = args[0] 39 | } else { 40 | manifestFile = "acon.json" 41 | } 42 | startingDir := filepath.Dir(manifestFile) 43 | if targetDir != "" { 44 | startingDir = targetDir 45 | } 46 | r, err := repo.FindRepo(startingDir) 47 | if err != nil { 48 | fmt.Fprintf(os.Stderr, "Generate Manifest: %v\n", err) 49 | return err 50 | } 51 | 52 | ctx := context.Background() 53 | cli, err := client.NewClientWithOpts(client.FromEnv, 54 | client.WithAPIVersionNegotiation()) 55 | if err != nil { 56 | fmt.Fprintf(os.Stderr, "Generate Manifest: %v\n", err) 57 | return err 58 | } 59 | 60 | // get and process the image layers 61 | imageStream, err := cli.ImageSave(ctx, []string{imageId}) 62 | if err != nil { 63 | fmt.Fprintf(os.Stderr, "Generate Manifest: cannot get image content: %v\n", err) 64 | return err 65 | } 66 | defer imageStream.Close() 67 | 68 | // get and transform the layer info 69 | inspect, _, err := cli.ImageInspectWithRaw(ctx, imageId) 70 | if err != nil { 71 | fmt.Fprintf(os.Stderr, "Generate Manifest: cannot inspect image: %v\n", err) 72 | return err 73 | } 74 | 75 | diffIds := []string{} 76 | dup := make([]bool, len(inspect.RootFS.Layers)) 77 | duplicate := make(map[string]int) 78 | for i, layer := range inspect.RootFS.Layers { 79 | if _, found := duplicate[layer]; !found { 80 | diffIds = append(diffIds, layer) 81 | dup[i] = false 82 | duplicate[layer]++ 83 | } else { 84 | dup[i] = true 85 | } 86 | } 87 | 88 | names, layers, err := fileutil.UntarBlob(imageStream) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | orderedLayers := [][]byte{} 94 | for i, name := range names { 95 | if !dup[i] { 96 | orderedLayers = append(orderedLayers, layers[name]) 97 | } 98 | } 99 | if err := r.CommitBlob(orderedLayers, diffIds); err != nil { 100 | return err 101 | } 102 | 103 | primaryLayers := make([]string, len(diffIds)) 104 | for i, layer := range diffIds { 105 | converted, err := r.PrimaryDigest(layer) 106 | if err != nil { 107 | fmt.Fprintf(os.Stderr, "Generate Manifest: cannot get primary hash for layer %s: %v\n", layer, err) 108 | return err 109 | } 110 | primaryLayers[i] = converted 111 | } 112 | 113 | // write to specified manifest file 114 | workingDir := inspect.Config.WorkingDir 115 | if workingDir == "" { 116 | workingDir = "/" 117 | } 118 | w := repo.Workload{MaxInstance: 1, 119 | SpecVersion: [2]uint32{1, 0}, 120 | WorkingDir: workingDir, 121 | Entrypoint: inspect.Config.Entrypoint, 122 | Env: inspect.Config.Env, 123 | Signals: make([]int32, 0), 124 | Uids: make([]uint32, 0), 125 | LogFDs: make([]uint32, 0)} 126 | 127 | _, err = os.Stat(manifestFile) 128 | if err == nil { 129 | // manifest file exists, update layers only 130 | content, err := os.ReadFile(manifestFile) 131 | if err != nil { 132 | fmt.Fprintf(os.Stderr, "Generate Manifest: cannot read manifest %s: %v\n", manifestFile, err) 133 | return err 134 | } 135 | if err := json.Unmarshal(content, &w); err != nil { 136 | fmt.Fprintf(os.Stderr, "Generate Manifest: cannot unmarshal manifest %s: %v\n", manifestFile, err) 137 | return err 138 | } 139 | } 140 | w.Layer = primaryLayers 141 | m, _ := json.MarshalIndent(w, "", " ") 142 | if err := os.WriteFile(manifestFile, m, 0600); err != nil { 143 | fmt.Fprintf(os.Stderr, "Generate Manifest: cannot write to %s: %v\n", manifestFile, err) 144 | return err 145 | } 146 | return nil 147 | } 148 | 149 | func init() { 150 | rootCmd.AddCommand(generateCmd) 151 | generateCmd.Flags().StringVarP(&imageId, "image", "i", "", 152 | "image to be used for the ACON container") 153 | generateCmd.MarkFlagRequired("image") 154 | } 155 | -------------------------------------------------------------------------------- /aconcli/service/auth.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | "syscall" 11 | "time" 12 | ) 13 | 14 | const ( 15 | UserRuntimeDir = "/run/user" 16 | AuthTableFile = "tokens.json" 17 | ) 18 | 19 | // map vmid to associated access token 20 | type AuthTable map[string]string 21 | 22 | func getUserAuthTable(f *os.File) (AuthTable, error) { 23 | finfo, err := f.Stat() 24 | if err != nil { 25 | return nil, err 26 | } 27 | size := finfo.Size() 28 | if size == 0 { 29 | return nil, nil 30 | } 31 | records := make([]byte, size) 32 | n, err := f.Read(records) 33 | if err != nil { 34 | return nil, err 35 | } 36 | var authTable AuthTable 37 | if err := json.Unmarshal(records[:n], &authTable); err != nil { 38 | return nil, err 39 | } 40 | return authTable, nil 41 | } 42 | 43 | func GetAuthToken(uid string, vmid string) (string, error) { 44 | f, err := os.Open(filepath.Join(UserRuntimeDir, uid, AuthTableFile)) 45 | if err != nil { 46 | return "", fmt.Errorf("failed to open auth file: %v", err) 47 | } 48 | defer f.Close() 49 | 50 | if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { 51 | return "", err 52 | } 53 | defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN) 54 | 55 | authTable, err := getUserAuthTable(f) 56 | if err != nil { 57 | return "", fmt.Errorf("failed to get auth token: %v", err) 58 | } 59 | if authTable == nil { 60 | return "", fmt.Errorf("empty auth table, no matching token found for %s", vmid) 61 | } 62 | token, ok := authTable[vmid] 63 | if !ok { 64 | return "", fmt.Errorf("no matching token found for %s", vmid) 65 | } 66 | return token, nil 67 | } 68 | 69 | func UpdateAuthToken(uid string, t AuthTable) error { 70 | filename := filepath.Join(UserRuntimeDir, uid, AuthTableFile) 71 | f, err := os.OpenFile(filename, os.O_RDWR|os.O_CREATE, 0600) 72 | if err != nil { 73 | return fmt.Errorf("failed to open auth file: %v", err) 74 | } 75 | defer f.Close() 76 | 77 | if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { 78 | return err 79 | } 80 | defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN) 81 | 82 | wholeTable, err := getUserAuthTable(f) 83 | if err != nil { 84 | return fmt.Errorf("failed to retrive whole auth data: %v", err) 85 | } 86 | if wholeTable == nil { 87 | wholeTable = AuthTable{} 88 | } 89 | current := time.Now().UTC().Unix() 90 | for vmid, sk := range wholeTable { 91 | expired, err := isExpired(sk, current) 92 | if err != nil { 93 | return fmt.Errorf("failed to determine expiration: %v", err) 94 | } 95 | if expired { 96 | delete(wholeTable, vmid) 97 | } 98 | } 99 | for k, v := range t { 100 | wholeTable[k] = v 101 | } 102 | authData, err := json.Marshal(wholeTable) 103 | if err != nil { 104 | return fmt.Errorf("failed to marshal auth data: %v\n", err) 105 | } 106 | 107 | f.Truncate(0) 108 | _, err = f.Seek(0, 0) 109 | if err != nil { 110 | return err 111 | } 112 | if _, err := f.Write(authData); err != nil { 113 | return fmt.Errorf("failed to write back auth data: %v\n", err) 114 | } 115 | return nil 116 | } 117 | 118 | func RemoveAuthToken(uid string, vmid string) error { 119 | filename := filepath.Join(UserRuntimeDir, uid, AuthTableFile) 120 | f, err := os.OpenFile(filename, os.O_RDWR, 0600) 121 | if err != nil { 122 | return fmt.Errorf("failed to open auth file: %v", err) 123 | } 124 | defer f.Close() 125 | 126 | if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { 127 | return err 128 | } 129 | defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN) 130 | 131 | wholeTable, err := getUserAuthTable(f) 132 | if err != nil { 133 | return fmt.Errorf("failed to retrive whole auth data: %v", err) 134 | } 135 | if wholeTable == nil { 136 | return fmt.Errorf("failed to remove auth token, empty auth table") 137 | } 138 | delete(wholeTable, vmid) 139 | 140 | current := time.Now().UTC().Unix() 141 | for vmid, sk := range wholeTable { 142 | expired, err := isExpired(sk, current) 143 | if err != nil { 144 | return fmt.Errorf("failed to determine expiration: %v", err) 145 | } 146 | if expired { 147 | delete(wholeTable, vmid) 148 | } 149 | } 150 | 151 | authData, err := json.Marshal(wholeTable) 152 | if err != nil { 153 | return fmt.Errorf("failed to marshal auth data: %v\n", err) 154 | } 155 | 156 | f.Truncate(0) 157 | _, err = f.Seek(0, 0) 158 | if err != nil { 159 | return err 160 | } 161 | if _, err := f.Write(authData); err != nil { 162 | return fmt.Errorf("failed to write back auth data: %v\n", err) 163 | } 164 | return nil 165 | } 166 | 167 | func IsLoggedIn(uid string, vmid string) (string, bool) { 168 | token, err := GetAuthToken(uid, vmid) 169 | if err != nil { 170 | return "", false 171 | } 172 | return token, true 173 | } 174 | 175 | func getExpirationFromSessionKey(sk string) (int64, error) { 176 | b := []byte(sk) 177 | var duration int64 178 | buf := bytes.NewReader(b[:8]) 179 | if err := binary.Read(buf, binary.LittleEndian, &duration); err != nil { 180 | return 0, err 181 | } 182 | return duration, nil 183 | } 184 | 185 | func isExpired(sk string, current int64) (bool, error) { 186 | expiration, err := getExpirationFromSessionKey(sk) 187 | if err != nil { 188 | return true, err 189 | } 190 | return current >= expiration, nil 191 | } 192 | -------------------------------------------------------------------------------- /acond/src/image.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use serde::{Deserialize, Serialize}; 5 | use std::collections::{BTreeMap, HashMap}; 6 | 7 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 8 | pub struct Alias { 9 | // Aliases refer to objects in acond's content store, such as FS layers. 10 | #[serde(default, skip_serializing_if = "HashMap::is_empty")] 11 | pub contents: HashMap>, 12 | // Aliases refer to objects defined inside the current Manifest being processed. 13 | // There's currently only one such object defined - ., which refers to the current Image. 14 | #[serde(default, skip_serializing_if = "HashMap::is_empty", rename = "self")] 15 | pub itself: HashMap>, 16 | } 17 | 18 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 19 | pub struct Policy { 20 | // Images that are allowed to share eTD with the enclosing Image. 21 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 22 | pub accepts: Vec, 23 | // True to reject all other Images not listed in accepts, default false. 24 | #[serde(default, rename = "rejectUnaccepted")] 25 | pub reject_unaccepted: bool, 26 | } 27 | 28 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 29 | pub struct Manifest { 30 | // Version of this spec in the form of [ MAJOR, MINOR ], and should be 31 | // [ 1, 0 ] for version 1.0. 32 | #[serde( 33 | default, 34 | skip_serializing_if = "Vec::is_empty", 35 | rename = "aconSpecVersion" 36 | )] 37 | pub acon_spec_version: Vec, 38 | // This is an array of FS layers from bottom to top (i.e., the same order 39 | // as in an OCI manifest) to be merged by overlay to form a Container's directory tree. 40 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 41 | pub layers: Vec, 42 | // This optional field defines Aliases of other objects, which could be 43 | // either FS layers or Images. More types of Aliases may be added in future. 44 | pub aliases: Alias, 45 | // This array of strings is passed to execve(2) syscall as the command arguments 46 | // to this Image's entry point, whose path is the first element of this same array.. 47 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 48 | pub entrypoint: Vec, 49 | // This lists environment variables (and optionally their acceptable values) settable 50 | // by untrusted code when executing this Image's entry point. 51 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 52 | pub env: Vec, 53 | // This is the working directory in which the Image's entrypoint should be executed. 54 | #[serde(rename = "workingDir")] 55 | pub working_dir: String, 56 | // These are additional UIDs that can be switched to by setuid(2) and seteuid(2) syscalls 57 | // inside a Container (launched from this Image). 58 | #[serde(default, skip_serializing_if = "Vec::is_empty")] 59 | pub uids: Vec, 60 | // This field lists file descriptors whose outputs contain no secrets and can be revealed to 61 | // untrusted entities. Outputs from these file descriptors may be captured by acond and made 62 | // available through acond's external interface. 63 | #[serde(default, skip_serializing_if = "Vec::is_empty", rename = "logFDs")] 64 | pub log_fds: Vec, 65 | // True to allow a Container to write to its directory tree, default false. 66 | #[serde(default, rename = "writableFS")] 67 | pub writable_fs: bool, 68 | // True to forbit a Container from being restarted, default false. 69 | #[serde(default, rename = "noRestart")] 70 | pub no_restart: bool, 71 | // This array of integers specifies the signals allowed to be sent by untrusted code, 72 | // default Empty - No signals are allowed. The first element also specifies the signal 73 | // to send upon restarting the Container. 74 | pub signals: Vec, 75 | // This must be an integer and is the maximal number of Container instances that can be 76 | // launched from this Image simultaneously, default 1 (singleton). 77 | #[serde(default, rename = "maxInstances")] 78 | pub max_instances: u64, 79 | // Specifies the Launch Policy that determines what other Images may share the same eTD with this Image. 80 | pub policy: Policy, 81 | } 82 | 83 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 84 | pub struct Measurement { 85 | pub tde: String, 86 | pub signer: String, 87 | } 88 | 89 | #[derive(Debug, Clone, PartialEq)] 90 | pub struct Image { 91 | pub id: String, 92 | pub hash_algorithm: String, 93 | pub signer_digest: String, 94 | pub signer_bytes: Vec, 95 | pub manifest_digest: String, 96 | pub manifest: Manifest, 97 | } 98 | 99 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 100 | #[serde(untagged)] 101 | pub enum AttestDataValue { 102 | NoDataValue {}, 103 | DataValue { 104 | #[serde(rename = "type")] 105 | dtype: i32, 106 | data: String, 107 | }, 108 | } 109 | 110 | #[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] 111 | pub struct AttestData { 112 | pub api_version: String, 113 | pub requestor_nonce: Vec, 114 | pub acond_nonce: Vec, 115 | pub attestation_data: BTreeMap>, 116 | } 117 | -------------------------------------------------------------------------------- /scripts/start-happypath.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Copyright (C) 2023 Intel Corporation 3 | # SPDX-License-Identifier: Apache-2.0 4 | 5 | log() { 6 | local type="$1"; shift 7 | local text="$*" 8 | local dt; dt="$(date --rfc-3339=seconds)" 9 | local color 10 | 11 | case "$type" in 12 | Note) 13 | color='\e[32m' # Green color for notes 14 | ;; 15 | Warn) 16 | color='\e[33m' # Yellow color for warnings 17 | ;; 18 | ERROR) 19 | color='\e[31m' # Red color for errors 20 | ;; 21 | esac 22 | 23 | # Reset color at the end of the message 24 | local reset_color='\e[m' 25 | 26 | echo -e "$dt $color[$type] $text$reset_color" 27 | } 28 | 29 | log_note() { 30 | log Note "$@" 31 | } 32 | 33 | log_warn() { 34 | log Warn "$@" >&2 35 | } 36 | 37 | log_error() { 38 | log ERROR "$@" >&2 39 | } 40 | 41 | get_options() { 42 | while getopts "d:i:f:j:o:h" opt; do 43 | case $opt in 44 | d) bundle_dir="$OPTARG";; 45 | i) docker_id="$OPTARG";; 46 | f) docker_file="$OPTARG";; 47 | j) jq_string="$OPTARG";; 48 | o) invoke="$OPTARG";; 49 | h) opt_h=1 50 | echo "Usage: run_workload -d bundle_dir -i container -f docker_file [-h]" 51 | ;; 52 | \?) echo "Invalid option: -$OPTARG" >&2 53 | return 1 54 | ;; 55 | esac 56 | done 57 | } 58 | 59 | run_workload() { 60 | get_options "$@" 61 | 62 | if [ "$opt_h" == 1 ]; then 63 | return 0 64 | fi 65 | 66 | if test -z "$jq_string"; then 67 | jq_string=".writableFS=true" 68 | fi 69 | 70 | test -d "$bundle_dir" && { 71 | log_warn "$bundle_dir directory already exist and will be cleared" 72 | rm -rf $bundle_dir 73 | } 74 | 75 | local readonly acon_root=$(git rev-parse --show-toplevel) 76 | test -d "$acon_root" || { 77 | log_error "Failed to deduce ACON root from current directory" 78 | return 2 79 | } 80 | 81 | log_note "Prepare kernel.img and OVMF.fd" 82 | git clone https://github.com/billionairiam/KernelAndOVFD.git $bundle_dir || { 83 | log_error "Failed to clone the repository." 84 | return 2 85 | } 86 | 87 | 88 | log_note "Build aconcli" 89 | cd "$acon_root/aconcli" && go generate && go build || { 90 | log_error "Build aconcli error." 91 | return 2 92 | } 93 | 94 | log_note "Build acond" 95 | source "$acon_root/scripts/acon-build.env" && U=. start_rust_buildenv -- ./build_static -r || { 96 | log_error "Build acond error or timeout" 97 | return 2 98 | } 99 | 100 | 101 | log_note "Generate initrd" 102 | cd ../$bundle_dir && mkdir initrd.d && INIT=/bin/acond gen_initrd initrd.d busybox:latest || { 103 | log_error "gen_initrd failed" 104 | return 2 105 | } 106 | 107 | log_note "Create initrd" 108 | cp "$acon_root/acond/target/release/acond" initrd.d/bin/acond 109 | create_initrd initrd.d/ ./initrd.img || { 110 | log_error "create_initrd failed" 111 | } 112 | 113 | log_note "Init bundle directory" 114 | cp "$acon_root/aconcli/aconcli" . && ./aconcli init . || { 115 | log_error "Init bundle directory error" 116 | return 2 117 | } 118 | 119 | log_note "Build bundle" 120 | if test -n "$docker_file"; then 121 | docker build -f "$docker_file" -t "$docker_id" . 122 | else 123 | docker pull "$docker_id" 124 | fi 125 | 126 | log_note "Generate Manifest" 127 | ./aconcli generate -i "$docker_id" "$docker_id.json" || { 128 | log_error "Generate Manifest error" 129 | return 2 130 | } 131 | 132 | log_note "Modify manifest file" 133 | jq "$jq_string" "$docker_id.json" 134 | cat <<< $(jq "$jq_string" "$docker_id.json") > "$docker_id.json" || { 135 | log_error "Append WritableFs:true to manifest failed" 136 | return 2 137 | } 138 | 139 | log_note "Generate KEY and CER" 140 | openssl ecparam -name secp521r1 -genkey -out signer.pem && openssl req -x509 -sha384 -key \ 141 | signer.pem -outform der -out signer.cer -subj /C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com 142 | 143 | log_note "Sign Manifest" 144 | ./aconcli sign -c signer.cer -k signer.pem "$docker_id.json" || { 145 | log_error "Sign Manifest error" 146 | return 2 147 | } 148 | 149 | log_note "run TDVM" 150 | ATD_BIOS=OVMF.fd ATD_KERNEL=kernel.img ATD_RD=initrd.img ./aconcli run -n "$docker_id.json" -c :5532 -f "$acon_root/scripts/acon-startvm" || { 151 | log_error "Run TDVM error will stop ACON instances" 152 | ./aconcli shutdown -f tcp://:5532 153 | return 2 154 | } 155 | 156 | log_note "Get TDVM status" 157 | ./aconcli status 158 | output=$(./aconcli status) 159 | instance_id=$(echo "$output" | awk '/Instance ID:/ {print $4}') 160 | 161 | if test -n "$invoke"; then 162 | log_note "Invoke TDVM" 163 | ./aconcli invoke -c tcp://:5532 -e "$instance_id" Whoami 164 | fi 165 | 166 | if test -n "$invoke"; then 167 | log_note "Invoke CheckUid" 168 | ./aconcli invoke -c tcp://:5532 -e "$instance_id" CheckUid 169 | fi 170 | 171 | log_note "Stop ACON instances" 172 | ./aconcli shutdown -f tcp://:5532 173 | } 174 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ACON - Attested Containers 2 | 3 | **NOTE**: This repo has been archived. The proposed event logging architecture has been adopted and implemented by [CNCF CoCo](https://www.cncf.io/projects/confidential-containers/) ([source code](https://github.com/confidential-containers/guest-components/tree/main/attestation-agent/attestation-agent/src/eventlog)). 4 | 5 | ACON is an open source architecture and solution to create and manage small, purpose-built, [Intel TDX][intel-tdx]-protected virtual machines (called Trust Domains, or TDs for short) for running container-like workloads. ACON aims at maximizing the security benefits of Intel TDX while retaining compatibility with existing applications/tools. It is designed and optimized for cloud native use cases (e.g., containers and *FaaS* (**F**unction **a**s **a** **S**ervice), etc.). 6 | 7 | ## Goals 8 | 9 | ACON is a container runtime. 10 | 11 | From security perspective, ACON is designed to have 12 | 13 | - Minimal TCB size. 14 | - Minimal attack surface. 15 | - Hardward based attestability of workloads and their runtime environment. 16 | 17 | From functional perspective, ACON is designed to be 18 | 19 | - Able to run existing (containerized) applications with minimal (or even no) changes. 20 | - Compatible with existing compiler toolchains, libraries, and debugging tools - for easy development and debugging of new applications. 21 | - Compatible with existing virtualization tools and container orchestration infrastructures - for easy integration with existing *CSP* (**C**loud **S**ervice **P**rovider) infrastructures. 22 | - Optimized for containers and *FaaS* usages where an application may be comprised of components from different/independent vendors. 23 | 24 | ## Architecture Overview 25 | 26 | The diagram blow depicts ACON's architecture at a highlevel, with its key components (highlighted in yellow) and unique (comparing to OCI compliant runtimes) features (highlighted in blue). 27 | 28 | ![Overview](doc/images/acon-overview.svg) 29 | 30 | In a nutshell, ACON runs containers in a small purpose-built [TD][intel-tdx] (referred to as the ACON TD, or *aTD*). Below an introduction is provided to the numbered items in the diagram above. 31 | 32 | 1. ACON Containers (referred to as *Container*s in *italics* hereon) are namespace-separated Linux processes similar to "containers" as defined in [OCI Runtime][oci-runtime-spec] spec. 33 | 2. *Container*s are launched from [ACON Image](doc/Image.md)s (referred to as just *Image*s hereon), which are similar to [OCI Image][oci-image-spec]s but with new elements designed specifically for cloud confidential compute usages. 34 | - *Image*s are always digitally signed - This associates an *Image* with its vendor cryptographically, and is necessary to support *Vendor FS Layers* (described below). 35 | - 🧩*Vendor FS layer* refers to the mechanism that allows the vendor of an *FS* (**F**ile **S**ystem) layer to update the layer on behalf of the *Image* owner. In the case of *FaaS* for example, this allows a *CSP* to update its *FaaS* framework on behalf of the function author. Details are available in the [ACON Image](doc/Image.md#aliases) spec. 36 | - 🔏*Launch Policy* (referred to as *Policy* for short hereon) is a part of an *Image* (hence is digitally signed) and governs *aTD* sharing - i.e., 2 *Image*s can be loaded into the same *aTD* if and only if their *Policies* are mutually acceptable. In practice, this is usually used by *Image* authors for specifying dependencies among *Image*s. Details are available in the [ACON Image](doc/Image.md#launch-policy) spec. 37 | 3. `aconcli` is a command line utility. It is used to 38 | - Convert OCI images into *Image*s and store them into a directory of a specific structure, called an ACON Image Repo (or just *Repo* for short). 39 | - Sign *Image*s in a *Repo*. 40 | - Start *aTD*s. 41 | - Load *Image*s from a *Repo* into an *aTD* and launch them as *Containers* (by sending commands to `acond`). 42 | 4. `acond` accepts *Image*s from `aconcli` via its *gRPC* interface (over *VSOCK* or *TCP*). Then, it measures each *Image* (using 📜*RTMR*s) and launches them as *Container*s. `acond` is launched as the *`init`* process (*PID* `1`) at boot and is the only privileged process (in the *aTD*) in production. 43 | - 📜*RTMR* (**R**un**T**ime **M**easurement **R**egister) is a TDX specific feature - As of TDX v1.0, every TD is equipped with 4 *RTMR*s (namely, *RTMR0*..*3*) of 384 bits each. An *RTMR* cannot be set to any specific value but can be only *extended* by software (i.e., it works in the same way as a *PCR* does in a *TPM*). 44 | 5. The *Guest Kernel* is an Intel TDX-enlightened Linux kernel specifically [configured](doc/TDGuest.md#configuring-linux-kernel) for minimal attack surface and TCB. 45 | 6. *Containers* may communicate with the outside world through *TCP* or *VSOCK*. And in the latter case a proxy may be necessary to bridge *VSOCK* and other protocols to allow communication with remote hosts. 46 | 47 | ## Getting Started 48 | 49 | See README.md files of [acond](acond/) and [aconcli](aconcli/) for build instructions. 50 | 51 | [doc/TDGuest.md](doc/TDGuest.md) details how to build TD guest kernel and initrd images. 52 | 53 | ## License 54 | 55 | This project is released under [Apache-2.0](LICENSE). 56 | 57 | [intel-tdx]: https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html 58 | [oci-image-spec]: https://github.com/opencontainers/image-spec/blob/main/spec.md 59 | [oci-runtime-spec]: https://github.com/opencontainers/runtime-spec/blob/main/spec.md 60 | [cncf-coco]: https://www.cncf.io/projects/confidential-containers/ 61 | [kata-ccv0]: https://github.com/confidential-containers/kata-containers-CCv0 -------------------------------------------------------------------------------- /aconcli/cmd/report.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cmd 5 | 6 | import ( 7 | "bytes" 8 | "encoding/binary" 9 | "encoding/hex" 10 | "errors" 11 | "fmt" 12 | "math/rand" 13 | "os" 14 | "strconv" 15 | "time" 16 | 17 | "aconcli/attest" 18 | "aconcli/service" 19 | "github.com/spf13/cobra" 20 | ) 21 | 22 | var ( 23 | isQuote bool 24 | file string 25 | ) 26 | 27 | func printReportType(reportType *attest.ReportType) { 28 | if reportType.Type == 0 { 29 | fmt.Fprintf(os.Stdout, "TEE Type: SGX\n") 30 | } else if reportType.Type == 0x81 { 31 | fmt.Fprintf(os.Stdout, "TEE Type: TDX\n") 32 | } 33 | fmt.Fprintf(os.Stdout, "Subtype: %v\n", reportType.Subtype) 34 | fmt.Fprintf(os.Stdout, "Version: %v\n", reportType.Version) 35 | } 36 | 37 | func printReportMac(reportMac *attest.ReportMac) { 38 | fmt.Fprintf(os.Stdout, "--- Report Mac Struct ---\n") 39 | printReportType(&reportMac.ReportType) 40 | fmt.Fprintf(os.Stdout, "CPU SVN: 0x%v\n", hex.EncodeToString(reportMac.CpuSvn[:])) 41 | fmt.Fprintf(os.Stdout, "TEE TCB Info Hash: 0x%v\n", reportMac.TeeTcbInfoHash) 42 | fmt.Fprintf(os.Stdout, "TEE Info Hash: 0x%v\n", reportMac.TeeInfoHash) 43 | fmt.Fprintf(os.Stdout, "Report Data: 0x%v\n", reportMac.ReportData) 44 | fmt.Fprintf(os.Stdout, "MAC: 0x%v\n", hex.EncodeToString(reportMac.Mac[:])) 45 | } 46 | 47 | func printTeeTcbInfo(teeTcbInfo *attest.TeeTcbInfo) { 48 | fmt.Fprintf(os.Stdout, "--- TEE TCB Info ---\n") 49 | fmt.Fprintf(os.Stdout, "0x%v\n", hex.EncodeToString(teeTcbInfo.Data[:])) 50 | } 51 | 52 | func printTdInfo(tdInfo *attest.TdInfo) { 53 | fmt.Fprintf(os.Stdout, "--- TD Info Struct ---\n") 54 | fmt.Fprintf(os.Stdout, "Attributes: %v\n", tdInfo.Attributes) 55 | fmt.Fprintf(os.Stdout, "XFAM: %v\n", tdInfo.Xfam) 56 | fmt.Fprintf(os.Stdout, "MRTD: %v\n", tdInfo.Mrtd) 57 | fmt.Fprintf(os.Stdout, "MR Config ID: %v\n", tdInfo.MrConfigId) 58 | fmt.Fprintf(os.Stdout, "MR Owner: %v\n", tdInfo.MrOwner) 59 | fmt.Fprintf(os.Stdout, "MR Owner Config: %v\n", tdInfo.MrOwnerConfig) 60 | fmt.Fprintf(os.Stdout, "RTMR:\n") 61 | for i := range [attest.NUM_RTMRS]int{} { 62 | fmt.Fprintf(os.Stdout, "%d: %v\n", i, tdInfo.Rtmr[i]) 63 | } 64 | fmt.Fprintf(os.Stdout, "Service TD Hash: %v\n", tdInfo.ServTdHash) 65 | } 66 | 67 | func parseReport(report []byte) error { 68 | if len(report) != 1024 { 69 | return errors.New("report data length error") 70 | } 71 | 72 | reportStruct := attest.TdReport{} 73 | err := binary.Read(bytes.NewReader(report), binary.LittleEndian, &reportStruct) 74 | if err != nil { 75 | return fmt.Errorf("parse report: %v\n", err) 76 | } 77 | printReportMac(&reportStruct.ReportMac) 78 | printTeeTcbInfo(&reportStruct.TeeTcbInfo) 79 | printTdInfo(&reportStruct.TdInfo) 80 | return nil 81 | } 82 | 83 | var reportCmd = &cobra.Command{ 84 | Use: "report [nonce-low] [nonce-high]", 85 | Short: "Request TD report or Quote", 86 | GroupID: "runtime", 87 | Long: ` 88 | Request a TD report or Quote from an ACON TD/VM. 89 | 90 | The ACON TD/VM must be specified by the '-c' flag. Use 'aconcli status' to list 91 | ACON TDs/VMs and ACON containers running in them. 92 | `, 93 | Args: cobra.MaximumNArgs(2), 94 | RunE: func(cmd *cobra.Command, args []string) error { 95 | return getReport(args) 96 | }, 97 | } 98 | 99 | func getReport(args []string) error { 100 | opts := []service.Opt{service.OptDialTLSContextInsecure()} 101 | if nologin { 102 | opts = append(opts, service.OptNoAuth()) 103 | } 104 | c, err := service.NewAconHttpConnWithOpts(vmConnTarget, opts...) 105 | if err != nil { 106 | fmt.Fprintf(os.Stderr, "Report: cannot connect to %s: %v\n", vmConnTarget, err) 107 | return err 108 | } 109 | 110 | var nl uint64 111 | var nh uint64 112 | s := rand.NewSource(time.Now().UnixNano()) 113 | r := rand.New(s) 114 | 115 | if len(args) == 0 { 116 | nl = r.Uint64() 117 | nh = r.Uint64() 118 | } 119 | if len(args) == 1 { 120 | nl, err = strconv.ParseUint(args[0], 0, 64) 121 | if err != nil { 122 | fmt.Fprintf(os.Stderr, "Report: cannot convert nonce low %s: %v\n", args[0], err) 123 | return err 124 | } 125 | nh = r.Uint64() 126 | } 127 | if len(args) == 2 { 128 | nl, err = strconv.ParseUint(args[0], 0, 64) 129 | if err != nil { 130 | fmt.Fprintf(os.Stderr, "Report: cannot convert nonce low %s: %v\n", args[0], err) 131 | return err 132 | } 133 | nh, err = strconv.ParseUint(args[1], 0, 64) 134 | if err != nil { 135 | fmt.Fprintf(os.Stderr, "Report: cannot convert nonce high %s: %v\n", args[1], err) 136 | return err 137 | } 138 | } 139 | 140 | var requestType uint32 141 | if isQuote { 142 | requestType = 1 143 | } else { 144 | requestType = 0 145 | } 146 | data, _, _, _, mrlog3, attest_data, err := c.Report(nl, nh, requestType) 147 | if err != nil { 148 | fmt.Fprintf(os.Stderr, "Report: cannot call 'report' service: %v\n", err) 149 | return err 150 | } 151 | fmt.Fprintf(os.Stdout, "mrlog3:\n%v\n", mrlog3) 152 | fmt.Fprintf(os.Stdout, "attestation data:\n%v\n", attest_data) 153 | 154 | if err := os.WriteFile(file, data, 0600); err != nil { 155 | return err 156 | } 157 | 158 | if isQuote { 159 | return nil 160 | } else { 161 | return parseReport(data) 162 | } 163 | } 164 | 165 | func init() { 166 | rootCmd.AddCommand(reportCmd) 167 | 168 | reportCmd.Flags().StringVarP(&vmConnTarget, "connect", "c", "", 169 | "connection target for the ACON virtual machine") 170 | reportCmd.MarkFlagRequired("conn") 171 | reportCmd.Flags().BoolVarP(&isQuote, "quote", "q", false, 172 | "getting quote instead of getting report") 173 | reportCmd.Flags().StringVarP(&file, "file", "f", "", 174 | "file path to dump the report or quote raw data") 175 | } 176 | -------------------------------------------------------------------------------- /samples/quote/client/main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package main 5 | 6 | import ( 7 | "bytes" 8 | "crypto/sha512" 9 | "encoding/binary" 10 | "encoding/hex" 11 | "encoding/json" 12 | "fmt" 13 | "io" 14 | "net" 15 | "os" 16 | "path/filepath" 17 | "strings" 18 | "time" 19 | 20 | "aconcli/attest" 21 | ) 22 | 23 | const ( 24 | RtmrLog0 uint = iota 25 | RtmrLog1 26 | RtmrLog2 27 | RtmrLog3 28 | ) 29 | 30 | const ( 31 | FinalizedLogEntry = "github.com/intel/ACON Finalize" 32 | Green = "\033[0;32m" 33 | Red = "\033[0;31m" 34 | Bold = "\033[1m" 35 | NoColor = "\033[0m" 36 | ) 37 | 38 | type QuoteHeader struct { 39 | RtmrLogOffset uint32 40 | AttestOffset uint32 41 | DataOffset uint32 42 | } 43 | 44 | func Doing(what string) { 45 | fmt.Printf(" %-60s", what+"...") 46 | time.Sleep(200 * time.Millisecond) 47 | } 48 | 49 | func Ok() { 50 | fmt.Println(Green + "Ok" + NoColor) 51 | } 52 | 53 | func Failed() { 54 | fmt.Println(Red + "Failed" + NoColor) 55 | } 56 | 57 | func Passed() { 58 | fmt.Println(Green + "Passed" + NoColor) 59 | } 60 | 61 | func dumpAttestInfo(a *attest.AttestData) { 62 | //fmt.Fprintf(os.Stdout, "api version: %v\n", a.ApiVersion) 63 | //fmt.Fprintf(os.Stdout, "requestor nonce: %v\n", a.RequestorNonce) 64 | //fmt.Fprintf(os.Stdout, "acond nonce: %v\n", a.AcondNonce) 65 | for imageId, cInfo := range a.AttestationData { 66 | fmt.Printf(Bold+"\tIMAGE"+NoColor+" -- %v\n", imageId[:18]) 67 | for i, v := range cInfo { 68 | fmt.Printf(Bold+"\t ContainerID"+NoColor+"\t%v\n", i) 69 | fmt.Printf(Bold+"\t Data"+NoColor+"\t%v\n", v.Data) 70 | } 71 | } 72 | } 73 | 74 | func main() { 75 | if len(os.Args) != 2 { 76 | fmt.Fprintf(os.Stderr, "usage: %v PORT\n", os.Args[0]) 77 | return 78 | } 79 | 80 | conn, err := net.Dial("tcp", "localhost:"+os.Args[1]) 81 | if err != nil { 82 | fmt.Println("connect error:", err) 83 | return 84 | } 85 | defer conn.Close() 86 | 87 | // get data from sample server 88 | Doing("Requesting quote from server") 89 | 90 | var data []byte 91 | buf := make([]byte, 1024) 92 | length := 0 93 | for { 94 | n, err := conn.Read(buf) 95 | if err != nil { 96 | if err != io.EOF { 97 | Failed() 98 | return 99 | } 100 | break 101 | } 102 | data = append(data, buf[:n]...) 103 | length += n 104 | } 105 | Ok() 106 | 107 | // parse quote, log and attestation data 108 | Doing("Parsing quote") 109 | 110 | header := new(QuoteHeader) 111 | err = binary.Read(bytes.NewReader(data), binary.LittleEndian, header) 112 | if err != nil { 113 | Failed() 114 | return 115 | } 116 | 117 | rtmrLog := data[header.RtmrLogOffset:header.AttestOffset] 118 | attestData := data[header.AttestOffset:header.DataOffset] 119 | quote := data[header.DataOffset:] 120 | 121 | quoteStruct, err := attest.ParseQuote(quote) 122 | if err != nil { 123 | Failed() 124 | return 125 | } 126 | Ok() 127 | 128 | q, err := json.MarshalIndent(quoteStruct, "", " ") 129 | if err != nil { 130 | fmt.Fprintf(os.Stderr, "marshal quote to json error: %v\n", err) 131 | return 132 | } 133 | 134 | if err := os.WriteFile("quote.json", q, 0600); err != nil { 135 | fmt.Fprintf(os.Stderr, "write out quote json file error: %v\n", err) 136 | return 137 | } 138 | 139 | if err := attest.WriteQuote("quote.bin", quote); err != nil { 140 | fmt.Fprintf(os.Stderr, "write out quote binary data error: %v\n", err) 141 | return 142 | } 143 | 144 | // verify quote using existing application from DCAP quote verify library 145 | Doing("Verifying quote") 146 | 147 | verifierPath := filepath.Dir(os.Args[0]) + "/app" 148 | ok, err := attest.VerifyQuote(verifierPath, "./quote.bin") 149 | if !ok { 150 | Failed() 151 | return 152 | } else { 153 | Ok() 154 | } 155 | 156 | Doing("Parsing RTMR activity log") 157 | logs := strings.Split(string(rtmrLog[RtmrLog3:]), "\x00") 158 | logs = logs[:len(logs)-1] 159 | Ok() 160 | 161 | // check whether evaluated rtmr value and rtmr value from quote match 162 | Doing("Verifying RTMR activity log") 163 | mr := hex.EncodeToString(attest.GetRtmrValue(logs)) 164 | r3 := quoteStruct.ReportBody.Rtmr[RtmrLog3] 165 | mrFromQuote := hex.EncodeToString(r3.M[:]) 166 | if mr != mrFromQuote { 167 | Failed() 168 | //fmt.Fprintf(os.Stderr, "Evaluated: %v\n", mr) 169 | //fmt.Fprintf(os.Stderr, "From quote: %v\n", mrFromQuote) 170 | return 171 | } else { 172 | Passed() 173 | //fmt.Fprintf(os.Stderr, "RTMR value: %v\n", mr) 174 | } 175 | 176 | // check whether evaluated reportdata and the value from quote match 177 | Doing("Verifying ReportData") 178 | rd := sha512.Sum384(attestData) 179 | rdHex := hex.EncodeToString(rd[:]) 180 | rdFromQuote := quoteStruct.ReportBody.ReportData 181 | rdFromQuoteHex := hex.EncodeToString(rdFromQuote.D[:sha512.Size384]) 182 | if rdHex != rdFromQuoteHex { 183 | Failed() 184 | return 185 | } else { 186 | Passed() 187 | } 188 | 189 | // check whether there exists a 'Finalized' log entry 190 | Doing("Checking RTMR log against security policy") 191 | finalizedlogFound := false 192 | for _, e := range logs { 193 | if e == FinalizedLogEntry { 194 | finalizedlogFound = true 195 | break 196 | } 197 | } 198 | if !finalizedlogFound { 199 | Failed() 200 | fmt.Printf("\t%sERROR%s\tACON TD is not finalized\n", Red, NoColor) 201 | return 202 | } 203 | Passed() 204 | 205 | // dislay attestation related information 206 | Doing("Extracting attestation data") 207 | a, err := attest.ParseAttestData(attestData) 208 | if err != nil { 209 | Failed() 210 | return 211 | } 212 | Ok() 213 | 214 | //fmt.Println(string(attestData[:])) 215 | dumpAttestInfo(a) 216 | } 217 | -------------------------------------------------------------------------------- /acond/src/oidc.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{anyhow, Result}; 2 | use chrono::{Duration, Utc}; 3 | use openidconnect::{ 4 | core::{ 5 | CoreAuthDisplay, CoreClaimName, CoreClaimType, CoreClient, CoreClientAuthMethod, 6 | CoreDeviceAuthorizationResponse, CoreGrantType, CoreJsonWebKey, CoreJsonWebKeyType, 7 | CoreJsonWebKeyUse, CoreJweContentEncryptionAlgorithm, CoreJweKeyManagementAlgorithm, 8 | CoreJwsSigningAlgorithm, CoreResponseMode, CoreResponseType, CoreSubjectIdentifierType, 9 | }, 10 | http::HeaderValue, 11 | reqwest::async_http_client, 12 | AdditionalProviderMetadata, AuthType, ClientId, ClientSecret, DeviceAuthorizationUrl, 13 | IssuerUrl, Nonce, NonceVerifier, ProviderMetadata, 14 | }; 15 | use openssl::{ 16 | hash::MessageDigest, 17 | pkey::{PKeyRef, Private}, 18 | sign::Signer, 19 | }; 20 | use serde::{Deserialize, Serialize}; 21 | use std::convert::TryInto; 22 | 23 | #[derive(Clone, Debug, Deserialize, Serialize)] 24 | struct DeviceEndpointProviderMetadata { 25 | device_authorization_endpoint: DeviceAuthorizationUrl, 26 | } 27 | impl AdditionalProviderMetadata for DeviceEndpointProviderMetadata {} 28 | type DeviceProviderMetadata = ProviderMetadata< 29 | DeviceEndpointProviderMetadata, 30 | CoreAuthDisplay, 31 | CoreClientAuthMethod, 32 | CoreClaimName, 33 | CoreClaimType, 34 | CoreGrantType, 35 | CoreJweContentEncryptionAlgorithm, 36 | CoreJweKeyManagementAlgorithm, 37 | CoreJwsSigningAlgorithm, 38 | CoreJsonWebKeyType, 39 | CoreJsonWebKeyUse, 40 | CoreJsonWebKey, 41 | CoreResponseMode, 42 | CoreResponseType, 43 | CoreSubjectIdentifierType, 44 | >; 45 | 46 | const TIME_OUT: u64 = 300; 47 | const GOOGLE_ISSUE_URL: &str = "https://accounts.google.com"; 48 | const ERR_IDP_ID_TOKEN_NOT_RETURN: &str = "Idp server doesn't return an ID token"; 49 | const ERR_IDP_INVALID_OPENID_USER: &str = "Invalid openid user from Idp server"; 50 | 51 | struct EmptyNonce; 52 | 53 | impl NonceVerifier for &EmptyNonce { 54 | fn verify(self, _: Option<&Nonce>) -> Result<(), String> { 55 | Ok(()) 56 | } 57 | } 58 | 59 | pub async fn verify_id_token( 60 | client_id: &str, 61 | client_secret: &str, 62 | device_code: &str, 63 | expires_in: u64, 64 | timeout: Option, 65 | openid_user: &Option, 66 | ) -> Result> { 67 | let client_id = ClientId::new(client_id.into()); 68 | let client_secret = ClientSecret::new(client_secret.into()); 69 | let issuer_url = IssuerUrl::new(GOOGLE_ISSUE_URL.into())?; 70 | 71 | let provider_metadata = 72 | DeviceProviderMetadata::discover_async(issuer_url, async_http_client).await?; 73 | 74 | let device_authorization_endpoint = provider_metadata 75 | .additional_metadata() 76 | .device_authorization_endpoint 77 | .clone(); 78 | 79 | let client = 80 | CoreClient::from_provider_metadata(provider_metadata, client_id, Some(client_secret)) 81 | .set_device_authorization_uri(device_authorization_endpoint.clone()) 82 | .set_auth_type(AuthType::RequestBody); 83 | 84 | let details: CoreDeviceAuthorizationResponse = serde_json::from_str( 85 | format!( 86 | r#"{{ 87 | "device_code": "{}", 88 | "user_code": "{}", 89 | "verification_uri": "{}", 90 | "expires_in": {} 91 | }}"#, 92 | device_code, 93 | device_code, 94 | device_authorization_endpoint.as_str(), 95 | expires_in 96 | ) 97 | .as_str(), 98 | )?; 99 | 100 | let token = client 101 | .exchange_device_access_token(&details) 102 | .request_async( 103 | async_http_client, 104 | tokio::time::sleep, 105 | Some(std::time::Duration::from_secs(TIME_OUT)), 106 | ) 107 | .await?; 108 | let id_token_claims = token 109 | .extra_fields() 110 | .id_token() 111 | .ok_or(anyhow!(ERR_IDP_ID_TOKEN_NOT_RETURN))? 112 | .claims(&client.id_token_verifier(), &EmptyNonce)?; 113 | 114 | match id_token_claims.email_verified() { 115 | Some(true) => (), 116 | _ => return Err(anyhow!(ERR_IDP_INVALID_OPENID_USER)), 117 | } 118 | 119 | if id_token_claims.email().map(|e| e.to_string()) != openid_user.clone() { 120 | return Err(anyhow!(ERR_IDP_INVALID_OPENID_USER)); 121 | } 122 | 123 | Ok(match timeout { 124 | Some(t) => (Utc::now() + Duration::seconds(t)).timestamp(), 125 | None => id_token_claims.expiration().timestamp(), 126 | } 127 | .to_ne_bytes() 128 | .to_vec()) 129 | } 130 | 131 | pub fn verify_secret(secret: &HeaderValue, pkey: &PKeyRef) -> bool { 132 | let secret_data = match data_encoding::HEXUPPER.decode(secret.as_bytes()) { 133 | Ok(data) => data, 134 | Err(_) => return false, 135 | }; 136 | 137 | let timestamp = match secret_data.get(0..8) { 138 | Some(timestamp) => timestamp, 139 | None => return false, 140 | }; 141 | let hmac = match secret_data.get(8..) { 142 | Some(hmac) => hmac, 143 | None => return false, 144 | }; 145 | 146 | let mut signer = match Signer::new(MessageDigest::sha384(), pkey) { 147 | Ok(signer) => signer, 148 | Err(_) => return false, 149 | }; 150 | if signer.update(timestamp).is_err() { 151 | return false; 152 | } 153 | let hmac_verified = match signer.sign_to_vec() { 154 | Ok(hmac) => hmac, 155 | Err(_) => return false, 156 | }; 157 | 158 | if hmac != hmac_verified.as_slice() { 159 | return false; 160 | } 161 | 162 | if Utc::now().timestamp() > i64::from_ne_bytes(timestamp.try_into().unwrap()) { 163 | return false; 164 | } 165 | 166 | true 167 | } 168 | -------------------------------------------------------------------------------- /acond/src/mount.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::utils; 5 | use anyhow::Result; 6 | use nix::mount::{self, MsFlags}; 7 | use std::{ 8 | fs::{self, File}, 9 | io::{BufRead, BufReader, ErrorKind}, 10 | os::unix::fs as unixfs, 11 | path::Path, 12 | }; 13 | 14 | lazy_static! { 15 | pub static ref SOFT_LINKS: Vec<(&'static str, &'static str)> = vec![ 16 | ("/proc/self/fd", "/dev/fd"), 17 | ("/proc/self/fd/0", "/dev/stdin"), 18 | ("/proc/self/fd/1", "/dev/stdout"), 19 | ("/proc/self/fd/2", "/dev/stderr") 20 | ]; 21 | pub static ref ROOTFS_MOUNTS: Vec = vec![ 22 | RootMount { 23 | source: None, 24 | target: "/dev", 25 | fstype: Some("devtmpfs"), 26 | flags: MsFlags::empty(), 27 | option: None 28 | }, 29 | RootMount { 30 | source: None, 31 | target: "/dev/pts", 32 | fstype: Some("devpts"), 33 | flags: MsFlags::empty(), 34 | option: None 35 | }, 36 | RootMount { 37 | source: None, 38 | target: "/proc", 39 | fstype: Some("proc"), 40 | flags: MsFlags::empty(), 41 | option: None 42 | }, 43 | RootMount { 44 | source: None, 45 | target: "/sys", 46 | fstype: Some("sysfs"), 47 | flags: MsFlags::empty(), 48 | option: None 49 | }, 50 | RootMount { 51 | source: None, 52 | target: "/shared", 53 | fstype: Some("tmpfs"), 54 | flags: MsFlags::empty(), 55 | option: Some("size=1m") 56 | }, 57 | RootMount { 58 | source: None, 59 | target: "/run", 60 | fstype: Some("tmpfs"), 61 | flags: MsFlags::empty(), 62 | option: Some("size=50%,mode=0755") 63 | }, 64 | ]; 65 | } 66 | 67 | #[derive(Debug, PartialEq)] 68 | pub struct RootMount { 69 | pub source: Option<&'static str>, 70 | pub target: &'static str, 71 | pub fstype: Option<&'static str>, 72 | pub flags: MsFlags, 73 | pub option: Option<&'static str>, 74 | } 75 | 76 | fn mount( 77 | source: Option<&str>, 78 | target: &str, 79 | fstype: Option<&str>, 80 | flags: MsFlags, 81 | option: Option<&str>, 82 | ) -> Result<()> { 83 | if !utils::is_mounted(target) { 84 | let path = Path::new(target); 85 | if !path.exists() { 86 | fs::create_dir(path)?; 87 | } 88 | 89 | mount::mount(source, target, fstype, flags, option)?; 90 | } 91 | 92 | Ok(()) 93 | } 94 | 95 | fn parse_mount_options(options_str: &str) -> (MsFlags, Vec) { 96 | let mut flags = MsFlags::empty(); 97 | let mut options = Vec::new(); 98 | 99 | for option in options_str.split(',') { 100 | match option { 101 | "defaults" => (), 102 | "ro" => flags |= MsFlags::MS_RDONLY, 103 | "nosuid" => flags |= MsFlags::MS_NOSUID, 104 | "nodev" => flags |= MsFlags::MS_NODEV, 105 | "noexec" => flags |= MsFlags::MS_NOEXEC, 106 | "sync" => flags |= MsFlags::MS_SYNCHRONOUS, 107 | "remount" => flags |= MsFlags::MS_REMOUNT, 108 | "dirsync" => flags |= MsFlags::MS_DIRSYNC, 109 | "diratime" => flags |= MsFlags::MS_NOATIME, 110 | "nodiratime" => flags |= MsFlags::MS_NODIRATIME, 111 | "silent" => flags |= MsFlags::MS_SILENT, 112 | "relatime" => flags |= MsFlags::MS_RELATIME, 113 | "strictatime" => flags |= MsFlags::MS_STRICTATIME, 114 | "lazytime" => flags |= MsFlags::MS_LAZYTIME, 115 | _ if option.contains('=') => options.push(option.into()), 116 | _ => log::error!("Mount option '{option}' is not supported."), 117 | } 118 | } 119 | 120 | (flags, options) 121 | } 122 | 123 | fn mount_fstab() -> Result<()> { 124 | let fstab_file = File::open("/etc/fstab")?; 125 | let reader = BufReader::new(fstab_file); 126 | 127 | for line in reader.lines() { 128 | let line = line?; 129 | if line.trim().starts_with('#') || line.trim().is_empty() { 130 | continue; 131 | } 132 | 133 | let fields: Vec<&str> = line.split_whitespace().collect(); 134 | if fields.len() < 4 { 135 | continue; 136 | } 137 | 138 | let source = Path::new(fields[0]); 139 | if source.is_absolute() && !source.exists() { 140 | continue; 141 | } 142 | 143 | let (flags, options) = parse_mount_options(fields[3]); 144 | if let Err(e) = mount( 145 | Some(fields[0]).filter(|s| *s != "none"), 146 | fields[1], 147 | Some(fields[2]).filter(|s| *s != "none"), 148 | flags, 149 | Some(options.join(",").as_str()).filter(|s| !s.is_empty()), 150 | ) { 151 | log::error!( 152 | "[/etc/fstab]: failed to mount {} to {} , error is {}.", 153 | fields[0], 154 | fields[1], 155 | e 156 | ); 157 | return Err(e); 158 | } 159 | } 160 | 161 | Ok(()) 162 | } 163 | 164 | pub fn mount_rootfs() -> Result<()> { 165 | if !utils::is_rootfs_mounted() && mount_fstab().is_err() { 166 | for m in ROOTFS_MOUNTS.iter() { 167 | mount(m.source, m.target, m.fstype, m.flags, m.option)?; 168 | } 169 | } 170 | 171 | for (original, link) in SOFT_LINKS.iter() { 172 | unixfs::symlink(original, link).or_else(|e| match e { 173 | ref e if e.kind() == ErrorKind::AlreadyExists => Ok(()), 174 | _ => Err(e), 175 | })?; 176 | } 177 | 178 | Ok(()) 179 | } 180 | -------------------------------------------------------------------------------- /aconcli/attest/attest.go: -------------------------------------------------------------------------------- 1 | package attest 2 | 3 | import ( 4 | "bytes" 5 | "crypto/sha512" 6 | "encoding/binary" 7 | "encoding/hex" 8 | "encoding/json" 9 | "fmt" 10 | "os" 11 | "os/exec" 12 | ) 13 | 14 | const ( 15 | NUM_RTMRS = 4 16 | rtmrSize = sha512.Size384 17 | ) 18 | 19 | type TdReport struct { 20 | ReportMac 21 | TeeTcbInfo 22 | Reserved [17]byte 23 | TdInfo 24 | } 25 | 26 | type ReportType struct { 27 | Type byte 28 | Subtype byte 29 | Version byte 30 | Reserved byte 31 | } 32 | 33 | type ReportMac struct { 34 | ReportType 35 | Reserved0 [12]byte 36 | CpuSvn [16]byte 37 | TeeTcbInfoHash TeeMeasurement 38 | TeeInfoHash TeeMeasurement 39 | ReportData TeeReportData 40 | Reserved1 [32]byte 41 | Mac [32]byte 42 | } 43 | 44 | type TeeTcbInfo struct { 45 | Data [239]byte 46 | } 47 | 48 | type TdInfo struct { 49 | Attributes TeeAttributes // TD's attributes 50 | Xfam TeeAttributes // TD's XFAM 51 | Mrtd TeeMeasurement // Measurement of initial contents of the TD 52 | MrConfigId TeeMeasurement // Software-defined ID for non-owner-defined configuration of the guest TD 53 | MrOwner TeeMeasurement // Software-defined ID for the guest TD's owner 54 | MrOwnerConfig TeeMeasurement // Software-defined ID for owner-defined configuration of the guest TD 55 | Rtmr [NUM_RTMRS]TeeMeasurement // Array of NUM_RTMRS(4) run-time extendable measurement registers 56 | ServTdHash TeeMeasurement 57 | Reserved [64]byte 58 | } 59 | 60 | type TeeMeasurement struct { 61 | M [48]byte 62 | } 63 | 64 | func (m TeeMeasurement) MarshalJSON() ([]byte, error) { 65 | src := m.M[:] 66 | return json.Marshal(hex.EncodeToString(src)) 67 | } 68 | func (m TeeMeasurement) String() string { 69 | return hex.EncodeToString(m.M[:]) 70 | } 71 | 72 | type TeeAttributes struct { 73 | A [2]uint32 74 | } 75 | 76 | func (a TeeAttributes) String() string { 77 | return fmt.Sprintf("%#x %#x", a.A[0], a.A[1]) 78 | } 79 | 80 | type TeeReportData struct { 81 | D [64]byte 82 | } 83 | 84 | func (r TeeReportData) MarshalJSON() ([]byte, error) { 85 | src := r.D[:] 86 | //fmt.Println(hex.EncodeToString(src)) 87 | return json.Marshal(hex.EncodeToString(src)) 88 | } 89 | 90 | type SGXQuote4Header struct { 91 | Version uint16 `json:"version"` 92 | AttKeyType uint16 `json:"-"` 93 | TeeType uint32 `json:"teeType"` 94 | Reserved uint32 `json:"-"` 95 | VendorId [16]byte `json:"-"` 96 | UserData [20]byte `json:"-"` 97 | } 98 | 99 | type SGXReport2Body struct { 100 | TeeTcbSvn [16]byte `json:"-"` 101 | MrSeam TeeMeasurement `json:"-"` 102 | MrSignerSeam TeeMeasurement `json:"-"` 103 | SeamAttributes TeeAttributes `json:"-"` 104 | TdAttributes TeeAttributes `json:"-"` 105 | Xfam TeeAttributes `json:"-"` 106 | MrTd TeeMeasurement `json:"-"` 107 | MrConfigId TeeMeasurement `json:"-"` 108 | MrOwner TeeMeasurement `json:"-"` 109 | MrOwnerConfig TeeMeasurement `json:"-"` 110 | Rtmr [NUM_RTMRS]TeeMeasurement `json:"rtmr"` 111 | ReportData TeeReportData `json:"reportData"` 112 | } 113 | 114 | type SGXQuote4 struct { 115 | Header SGXQuote4Header `json:"header"` 116 | ReportBody SGXReport2Body `json:"reportBody"` 117 | SigDataLen uint32 `json:"-"` 118 | //SigData []byte 119 | } 120 | 121 | type AttestData struct { 122 | ApiVersion string `json:"api_version"` 123 | RequestorNonce []byte `json:"requestor_nonce"` 124 | AcondNonce []byte `json:"acond_nonce"` 125 | AttestationData map[string]map[uint32]AttestDataValue `json:"attestation_data"` 126 | } 127 | 128 | type AttestDataValue struct { 129 | Type int32 `json:"type"` 130 | Data string `json:"data"` 131 | } 132 | 133 | func ParseQuote(quote []byte) (*SGXQuote4, error) { 134 | quoteStruct := new(SGXQuote4) 135 | err := binary.Read(bytes.NewReader(quote), binary.LittleEndian, quoteStruct) 136 | if err != nil { 137 | return nil, fmt.Errorf("parse quote: %v", err) 138 | } 139 | return quoteStruct, nil 140 | } 141 | 142 | func ParseAttestData(data []byte) (*AttestData, error) { 143 | attestData := new(AttestData) 144 | err := json.Unmarshal(data, attestData) 145 | if err != nil { 146 | return nil, fmt.Errorf("unmarshal attest data error: %v", err) 147 | } 148 | return attestData, nil 149 | } 150 | 151 | func WriteQuote(filename string, quote []byte) error { 152 | f, err := os.Create(filename) 153 | if err != nil { 154 | return err 155 | } 156 | defer f.Close() 157 | 158 | _, err = f.Write(quote) 159 | return err 160 | } 161 | 162 | func VerifyQuote(verifier, quote string) (bool, error) { 163 | cmd := exec.Command(verifier, "-quote", quote) 164 | if err := cmd.Run(); err != nil { 165 | return false, err 166 | } else { 167 | return true, nil 168 | } 169 | } 170 | 171 | func GetRtmrValue(logs []string) []byte { 172 | result := make([]byte, rtmrSize) 173 | for _, log := range logs { 174 | logSum := sha512.Sum384([]byte(log)) 175 | result = append(result, logSum[:]...) 176 | sum := sha512.Sum384(result) 177 | result = sum[:] 178 | } 179 | return result 180 | } 181 | 182 | func VerifyRtmr(quote []byte, logs []string) (bool, error) { 183 | quoteStruct, err := ParseQuote(quote) 184 | if err != nil { 185 | return false, fmt.Errorf("verify rtmr error: %v", err) 186 | } 187 | mr := hex.EncodeToString(GetRtmrValue(logs)) 188 | r := quoteStruct.ReportBody.Rtmr[3] 189 | mrFromQuote := hex.EncodeToString(r.M[:]) 190 | if mr == mrFromQuote { 191 | return true, nil 192 | } else { 193 | return false, fmt.Errorf("rtmr does not match") 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /aconcli/cryptoutil/key.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package cryptoutil 5 | 6 | import ( 7 | "crypto" 8 | "crypto/ecdsa" 9 | "crypto/ed25519" 10 | "crypto/rand" 11 | "crypto/rsa" 12 | "crypto/x509" 13 | "encoding/pem" 14 | "errors" 15 | "io/ioutil" 16 | "path/filepath" 17 | "strings" 18 | ) 19 | 20 | // Attemp to parse the given private key DER block. 21 | // Try PKCS#1, PKCS#8 and SEC1 EC private keys. 22 | func parsePrivateKey(der []byte) (crypto.PrivateKey, error) { 23 | if key, err := x509.ParsePKCS1PrivateKey(der); err == nil { 24 | return key, nil 25 | } 26 | if key, err := x509.ParsePKCS8PrivateKey(der); err == nil { 27 | switch key := key.(type) { 28 | case *rsa.PrivateKey, *ecdsa.PrivateKey, ed25519.PrivateKey: 29 | return key, nil 30 | default: 31 | return nil, errors.New("cryptoutil: Unknown private key type in PKCS#8 wrapping") 32 | } 33 | } 34 | if key, err := x509.ParseECPrivateKey(der); err == nil { 35 | return key, nil 36 | } 37 | 38 | return nil, errors.New("cryptoutil: Failed to parse private key") 39 | } 40 | 41 | // Parse the private key file in PEM format to DER block 42 | func parsePrivateKeyPEMFile(keyFile string) ([]byte, error) { 43 | keyPEMBlock, err := ioutil.ReadFile(filepath.Clean(keyFile)) 44 | if err != nil { 45 | return nil, err 46 | } 47 | 48 | var keyDERBlock *pem.Block 49 | for { 50 | keyDERBlock, keyPEMBlock = pem.Decode(keyPEMBlock) 51 | if keyDERBlock == nil { 52 | return nil, errors.New("cryptoutil: Failed to parse key PEM data") 53 | } 54 | 55 | if x509.IsEncryptedPEMBlock(keyDERBlock) { 56 | return nil, errors.New("cryptoutil: Encrypted private key Not supported") 57 | } 58 | 59 | if keyDERBlock.Type == "PRIVATE KEY" || strings.HasSuffix(keyDERBlock.Type, "PRIVATE KEY") { 60 | break 61 | } 62 | } 63 | return keyDERBlock.Bytes, nil 64 | } 65 | 66 | func verify(certDer []byte, privKey crypto.PrivateKey) (err error) { 67 | x509Cert, err := x509.ParseCertificate(certDer) 68 | if err != nil { 69 | return 70 | } 71 | 72 | switch pub := x509Cert.PublicKey.(type) { 73 | case *rsa.PublicKey: 74 | priv, ok := privKey.(*rsa.PrivateKey) 75 | if !ok { 76 | err = errors.New("cryptoutil: private key type does not match public key type") 77 | return 78 | } 79 | if pub.N.Cmp(priv.N) != 0 { 80 | err = errors.New("cryptoutil: private key does not match public key") 81 | return 82 | } 83 | case *ecdsa.PublicKey: 84 | priv, ok := privKey.(*ecdsa.PrivateKey) 85 | if !ok { 86 | err = errors.New("cryptoutil: private key type does not match public key type") 87 | return 88 | 89 | } 90 | if pub.X.Cmp(priv.X) != 0 || pub.Y.Cmp(priv.Y) != 0 { 91 | err = errors.New("cryptoutil: private key does not match public key") 92 | return 93 | } 94 | case ed25519.PublicKey: 95 | priv, ok := privKey.(ed25519.PrivateKey) 96 | if !ok { 97 | err = errors.New("cryptoutil: private key type does not match public key type") 98 | return 99 | } 100 | equal := pub.Equal(priv.Public()) 101 | if !equal { 102 | err = errors.New("cryptoutil: private key does not match public key") 103 | return 104 | } 105 | default: 106 | err = errors.New("cryptoutil: unknown public key algorithm") 107 | return 108 | } 109 | return 110 | } 111 | 112 | func Sign(message []byte, certFile, keyFile string) ([]byte, error) { 113 | derBlock, err := parsePrivateKeyPEMFile(keyFile) 114 | if err != nil { 115 | return nil, err 116 | } 117 | 118 | privKey, err := parsePrivateKey(derBlock) 119 | if err != nil { 120 | return nil, err 121 | } 122 | 123 | hash, err := GetHashAlgoFromCert(certFile) 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | certDer, err := parseCertFile(certFile) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | if err := verify(certDer, privKey); err != nil { 134 | return nil, err 135 | } 136 | 137 | switch privKey := privKey.(type) { 138 | case *rsa.PrivateKey: 139 | hashAlgo, err := getHashAlgo(hash) 140 | if err != nil { 141 | return nil, err 142 | } 143 | digest, err := genericHash(message, hashAlgo) 144 | if err != nil { 145 | return nil, err 146 | } 147 | return rsa.SignPKCS1v15(rand.Reader, privKey, hashAlgo, digest) 148 | 149 | case *ecdsa.PrivateKey: 150 | hashAlgo, err := getHashAlgo(hash) 151 | if err != nil { 152 | return nil, err 153 | } 154 | digest, err := genericHash(message, hashAlgo) 155 | if err != nil { 156 | return nil, err 157 | } 158 | return ecdsa.SignASN1(rand.Reader, privKey, digest) 159 | 160 | case ed25519.PrivateKey: 161 | return ed25519.Sign(privKey, message), nil 162 | 163 | default: 164 | return nil, errors.New("cryptoutil: Unknown private key type") 165 | } 166 | } 167 | 168 | func Verify(message, sig []byte, certFile string) bool { 169 | 170 | certDer, err := parseCertFile(certFile) 171 | if err != nil { 172 | return false 173 | } 174 | 175 | hash, err := GetHashAlgoFromCert(certFile) 176 | if err != nil { 177 | return false 178 | } 179 | 180 | x509Cert, err := x509.ParseCertificate(certDer) 181 | if err != nil { 182 | return false 183 | } 184 | 185 | switch pub := x509Cert.PublicKey.(type) { 186 | case *rsa.PublicKey: 187 | hashAlgo, err := getHashAlgo(hash) 188 | if err != nil { 189 | return false 190 | } 191 | digest, err := genericHash(message, hashAlgo) 192 | if err != nil { 193 | return false 194 | } 195 | err = rsa.VerifyPKCS1v15(pub, hashAlgo, digest, sig) 196 | return err == nil 197 | 198 | case *ecdsa.PublicKey: 199 | hashAlgo, err := getHashAlgo(hash) 200 | if err != nil { 201 | return false 202 | } 203 | digest, err := genericHash(message, hashAlgo) 204 | if err != nil { 205 | return false 206 | } 207 | return ecdsa.VerifyASN1(pub, digest, sig) 208 | 209 | case ed25519.PublicKey: 210 | return ed25519.Verify(pub, message, sig) 211 | 212 | default: 213 | return false 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | CommunityCodeOfConduct AT intel DOT com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [Mozilla CoC]: https://github.com/mozilla/diversity 131 | [FAQ]: https://www.contributor-covenant.org/faq 132 | -------------------------------------------------------------------------------- /aconcli/service/aconclient_grpc.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "log" 7 | "os" 8 | "path/filepath" 9 | "time" 10 | 11 | nc "aconcli/netconn" 12 | pb "aconcli/proto" 13 | "google.golang.org/grpc" 14 | emptypb "google.golang.org/protobuf/types/known/emptypb" 15 | ) 16 | 17 | const ( 18 | DefaultServiceTimeout = 60 * time.Second 19 | ) 20 | 21 | type AconClientGrpc struct { 22 | pb.AconServiceClient 23 | conn *grpc.ClientConn 24 | } 25 | 26 | // caller's responsibility to call Close() on the returned AconClient 27 | // after using the agent services 28 | func NewAconGrpcConnection(targetConn string) (*AconClientGrpc, error) { 29 | log.Println("Service: Connecting", targetConn) 30 | conn, err := nc.NewConnection(targetConn) 31 | if err != nil { 32 | return nil, err 33 | } 34 | log.Println("Service: Connected") 35 | return &AconClientGrpc{ 36 | AconServiceClient: pb.NewAconServiceClient(conn), 37 | conn: conn, 38 | }, nil 39 | } 40 | 41 | func (c *AconClientGrpc) Close() error { 42 | return c.conn.Close() 43 | } 44 | 45 | func (c *AconClientGrpc) AddManifest(manifestPath, sigPath, certPath string) (string, []string, error) { 46 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 47 | defer cancel() 48 | 49 | aconJSON, err := os.ReadFile(manifestPath) 50 | if err != nil { 51 | return "", nil, err 52 | } 53 | 54 | var v interface{} 55 | if err := json.Unmarshal(aconJSON, &v); err != nil { 56 | return "", nil, err 57 | } 58 | aconJSON, err = json.Marshal(v) 59 | if err != nil { 60 | return "", nil, err 61 | } 62 | 63 | sig, err := os.ReadFile(sigPath) 64 | if err != nil { 65 | return "", nil, err 66 | } 67 | 68 | cert, err := os.ReadFile(certPath) 69 | if err != nil { 70 | return "", nil, err 71 | } 72 | r, err := c.AconServiceClient.AddManifest(ctx, 73 | &pb.AddManifestRequest{Manifest: string(aconJSON), 74 | Signature: sig, 75 | Certificate: cert, 76 | }) 77 | if err != nil { 78 | return "", nil, err 79 | } 80 | return r.GetImageId(), r.GetMissingLayers(), nil 81 | } 82 | 83 | func (c *AconClientGrpc) AddBlob(alg uint32, blobpath string) error { 84 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 85 | defer cancel() 86 | 87 | content, err := os.ReadFile(filepath.Clean(blobpath)) 88 | if err != nil { 89 | return err 90 | } 91 | _, err = c.AconServiceClient.AddBlob(ctx, &pb.AddBlobRequest{Alg: alg, Data: content}) 92 | return err 93 | } 94 | 95 | func (c *AconClientGrpc) Finalize() error { 96 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 97 | defer cancel() 98 | 99 | _, err := c.AconServiceClient.Finalize(ctx, &emptypb.Empty{}) 100 | return err 101 | } 102 | 103 | func (c *AconClientGrpc) Start(imageId string, env []string) (uint32, error) { 104 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 105 | defer cancel() 106 | 107 | r, err := c.AconServiceClient.Start(ctx, &pb.StartRequest{ImageId: imageId, Envs: env}) 108 | if err != nil { 109 | return 0, err 110 | } 111 | return r.GetContainerId(), nil 112 | } 113 | 114 | func (c *AconClientGrpc) Kill(cid uint32, signum int32) error { 115 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 116 | defer cancel() 117 | 118 | _, err := c.AconServiceClient.Kill(ctx, &pb.KillRequest{ContainerId: cid, SignalNum: signum}) 119 | return err 120 | } 121 | 122 | func (c *AconClientGrpc) Restart(cid uint32, timeout uint64) error { 123 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout+time.Duration(timeout)*time.Second) 124 | defer cancel() 125 | 126 | _, err := c.AconServiceClient.Restart(ctx, &pb.RestartRequest{ContainerId: cid, Timeout: timeout}) 127 | return err 128 | } 129 | 130 | func (c *AconClientGrpc) Invoke(cid uint32, invocation []string, 131 | timeout uint64, env []string, datafile string, capture_size uint64) ([]byte, []byte, error) { 132 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout+time.Duration(timeout)*time.Second) 133 | defer cancel() 134 | 135 | cmd := invocation[0] 136 | var args []string 137 | if len(invocation) > 1 { 138 | args = invocation[1:] 139 | } 140 | 141 | var data []byte 142 | if datafile != "" { 143 | d, err := os.ReadFile(filepath.Clean(datafile)) 144 | if err != nil { 145 | return nil, nil, err 146 | } 147 | data = d 148 | } 149 | 150 | r, err := c.AconServiceClient.Exec(ctx, &pb.ExecRequest{ 151 | ContainerId: cid, 152 | Command: cmd, 153 | Timeout: timeout, 154 | Arguments: args, 155 | Envs: env, 156 | Stdin: data, 157 | CaptureSize: capture_size}) 158 | if err != nil { 159 | return nil, nil, err 160 | } 161 | return r.GetStdout(), r.GetStderr(), nil 162 | } 163 | 164 | func (c *AconClientGrpc) Inspect(cid uint32) ([]AconStatus, error) { 165 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 166 | defer cancel() 167 | 168 | r, err := c.AconServiceClient.Inspect(ctx, &pb.InspectRequest{ContainerId: cid}) 169 | if err != nil { 170 | return nil, err 171 | } 172 | 173 | // cid == 0 indicates getting status for all acon instances 174 | // otherwise, get the status for the specified acon. 175 | status := r.GetInfo() 176 | result := make([]AconStatus, 0, len(status)) 177 | for _, s := range status { 178 | result = append(result, AconStatus{ContainerId: s.GetContainerId(), 179 | State: s.GetState(), 180 | Wstatus: s.GetWstatus(), 181 | ImageId: s.GetImageId(), 182 | ExePath: s.GetExePath()}) 183 | } 184 | return result, nil 185 | } 186 | 187 | func (c *AconClientGrpc) Report(nonceLo, nonceHi uint64, requestType uint32) (data []byte, 188 | mrlog0 []string, mrlog1 []string, mrlog2 []string, mrlog3 []string, attest_data string, e error) { 189 | ctx, cancel := context.WithTimeout(context.Background(), DefaultServiceTimeout) 190 | defer cancel() 191 | 192 | r, err := c.AconServiceClient.Report(ctx, &pb.ReportRequest{NonceLo: nonceLo, NonceHi: nonceHi, RequestType: requestType}) 193 | if err != nil { 194 | e = err 195 | return 196 | } 197 | data = r.GetData() 198 | mrlogs := r.GetMrlog() 199 | mrlog0 = mrlogs[0].GetLogs() 200 | mrlog1 = mrlogs[1].GetLogs() 201 | mrlog2 = mrlogs[2].GetLogs() 202 | mrlog3 = mrlogs[3].GetLogs() 203 | attest_data = r.GetAttestationData() 204 | return 205 | } 206 | -------------------------------------------------------------------------------- /aconcli/netconn/connection.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | package netconn 5 | 6 | import ( 7 | "context" 8 | "net" 9 | "net/url" 10 | "strconv" 11 | "strings" 12 | "time" 13 | 14 | "github.com/mdlayher/vsock" 15 | "google.golang.org/grpc" 16 | "google.golang.org/grpc/codes" 17 | "google.golang.org/grpc/credentials/insecure" 18 | grpcStatus "google.golang.org/grpc/status" 19 | ) 20 | 21 | const ( 22 | UnixSocketScheme = "unix" 23 | VSockSocketScheme = "vsock" 24 | TCPSocketScheme = "tcp" 25 | defaultDialTimeout = 30 * time.Second 26 | ) 27 | 28 | func parse(sock string) (string, *url.URL, error) { 29 | addr, err := url.Parse(sock) 30 | if err != nil { 31 | return "", nil, err 32 | } 33 | 34 | var grpcAddr string 35 | switch addr.Scheme { 36 | case VSockSocketScheme: 37 | if addr.Hostname() == "" || addr.Port() == "" || addr.Path != "" { 38 | return "", nil, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock scheme: %s", sock) 39 | } 40 | if _, err := strconv.ParseUint(addr.Hostname(), 10, 32); err != nil { 41 | return "", nil, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock cid: %s", sock) 42 | } 43 | if _, err := strconv.ParseUint(addr.Port(), 10, 32); err != nil { 44 | return "", nil, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock port: %s", sock) 45 | } 46 | grpcAddr = VSockSocketScheme + ":" + addr.Host 47 | case TCPSocketScheme: 48 | grpcAddr = TCPSocketScheme + ":" + addr.Host 49 | case UnixSocketScheme: 50 | fallthrough 51 | case "": 52 | if (addr.Host == "" && addr.Path == "") || addr.Port() != "" { 53 | return "", nil, grpcStatus.Errorf(codes.InvalidArgument, "Invalid unix scheme: %s", sock) 54 | } 55 | if addr.Host == "" { 56 | grpcAddr = UnixSocketScheme + ":///" + addr.Path 57 | } else { 58 | grpcAddr = UnixSocketScheme + ":///" + addr.Host + "/" + addr.Path 59 | } 60 | default: 61 | return "", nil, grpcStatus.Errorf(codes.InvalidArgument, "Invalid scheme: %s", sock) 62 | } 63 | 64 | return grpcAddr, addr, nil 65 | } 66 | 67 | type dialer func(string, time.Duration) (net.Conn, error) 68 | 69 | func agentDialer(addr *url.URL) dialer { 70 | var d dialer 71 | switch addr.Scheme { 72 | case VSockSocketScheme: 73 | d = vsockDialer 74 | case TCPSocketScheme: 75 | d = tcpDialer 76 | case UnixSocketScheme: 77 | fallthrough 78 | default: 79 | d = unixDialer 80 | } 81 | return d 82 | } 83 | 84 | func unixDialer(sock string, timeout time.Duration) (net.Conn, error) { 85 | if strings.HasPrefix(sock, "unix:") { 86 | sock = strings.Trim(sock, "unix:") 87 | } 88 | 89 | dialFunc := func() (net.Conn, error) { 90 | return net.DialTimeout("unix", sock, timeout) 91 | } 92 | 93 | timeoutErr := grpcStatus.Errorf(codes.DeadlineExceeded, "timed out connecting to unix socket %s", sock) 94 | return commonDialer(timeout, dialFunc, timeoutErr) 95 | } 96 | 97 | func vsockDialer(sock string, timeout time.Duration) (net.Conn, error) { 98 | cid, port, err := parseGrpcVsockAddr(sock) 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | dialFunc := func() (net.Conn, error) { 104 | return vsock.Dial(cid, port, nil) 105 | } 106 | 107 | timeoutErr := grpcStatus.Errorf(codes.DeadlineExceeded, "timed out connecting to vsock %d:%d", cid, port) 108 | 109 | return commonDialer(timeout, dialFunc, timeoutErr) 110 | } 111 | 112 | func tcpDialer(sock string, timeout time.Duration) (net.Conn, error) { 113 | if !strings.HasPrefix(sock, "tcp:") { 114 | return nil, grpcStatus.Errorf(codes.InvalidArgument, "Invalid tcp URL scheme: %s", sock) 115 | } 116 | sock = strings.TrimPrefix(sock, "tcp:") 117 | 118 | dialFunc := func() (net.Conn, error) { 119 | return net.Dial("tcp", sock) 120 | } 121 | 122 | timeoutErr := grpcStatus.Errorf(codes.DeadlineExceeded, "timed out connecting to tcp socket %s", sock) 123 | return commonDialer(timeout, dialFunc, timeoutErr) 124 | } 125 | 126 | func commonDialer(timeout time.Duration, dialFunc func() (net.Conn, error), timeoutErrMsg error) (net.Conn, error) { 127 | t := time.NewTimer(timeout) 128 | cancel := make(chan bool) 129 | ch := make(chan net.Conn) 130 | go func() { 131 | for { 132 | select { 133 | case <-cancel: 134 | // canceled or channel closed 135 | return 136 | default: 137 | } 138 | 139 | conn, err := dialFunc() 140 | if err == nil { 141 | // Send conn back iff timer is not fired 142 | // Otherwise there might be no one left reading it 143 | if t.Stop() { 144 | ch <- conn 145 | } else { 146 | conn.Close() 147 | } 148 | return 149 | } 150 | } 151 | }() 152 | 153 | var conn net.Conn 154 | var ok bool 155 | select { 156 | case conn, ok = <-ch: 157 | if !ok { 158 | return nil, timeoutErrMsg 159 | } 160 | case <-t.C: 161 | cancel <- true 162 | return nil, timeoutErrMsg 163 | } 164 | 165 | return conn, nil 166 | } 167 | 168 | func parseGrpcVsockAddr(sock string) (uint32, uint32, error) { 169 | sp := strings.Split(sock, ":") 170 | if len(sp) != 3 { 171 | return 0, 0, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock address: %s", sock) 172 | } 173 | if sp[0] != VSockSocketScheme { 174 | return 0, 0, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock URL scheme: %s", sp[0]) 175 | } 176 | 177 | cid, err := strconv.ParseUint(sp[1], 10, 32) 178 | if err != nil { 179 | return 0, 0, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock cid: %s", sp[1]) 180 | } 181 | port, err := strconv.ParseUint(sp[2], 10, 32) 182 | if err != nil { 183 | return 0, 0, grpcStatus.Errorf(codes.InvalidArgument, "Invalid vsock port: %s", sp[2]) 184 | } 185 | 186 | return uint32(cid), uint32(port), nil 187 | } 188 | 189 | func NewConnection(sock string) (*grpc.ClientConn, error) { 190 | ctx := context.Background() 191 | grpcAddr, parsedAddr, err := parse(sock) 192 | if err != nil { 193 | return nil, err 194 | } 195 | 196 | dialOpts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials()), 197 | grpc.WithBlock(), 198 | // explicitly set ':authority' pseudo header to avoid HTTP2 Protocol Error 199 | grpc.WithAuthority("acond"), 200 | grpc.WithDialer(agentDialer(parsedAddr))} 201 | 202 | ctx, cancel := context.WithTimeout(ctx, defaultDialTimeout) 203 | defer cancel() 204 | 205 | conn, err := grpc.DialContext(ctx, grpcAddr, dialOpts...) 206 | if err != nil { 207 | return nil, err 208 | } 209 | 210 | return conn, nil 211 | } 212 | -------------------------------------------------------------------------------- /acond/src/config.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | use crate::utils; 5 | use anyhow::{anyhow, Result}; 6 | use std::fs; 7 | 8 | const KERNEL_CMDLINE: &str = "/proc/cmdline"; 9 | const KEY_VSOCK_CONN: &str = "acond.vsock_conn"; 10 | const KEY_VSOCK_PORT: &str = "acond.vsock_port"; 11 | const KEY_TCP_PORT: &str = "acond.tcp_port"; 12 | const KEY_TIMEOUT: &str = "acond.timeout"; 13 | const KEY_OPENID_USER: &str = "acond.openid_user"; 14 | const KEY_HTTPS_PROXY: &str = "acond.https_proxy"; 15 | const DEF_VSOCK_PORT: u32 = 1024; 16 | const DEF_TCP_PORT: u32 = 1025; 17 | const DEF_TIMEOUT: u32 = 180; 18 | 19 | #[derive(Debug)] 20 | pub struct Config { 21 | pub vsock_conn: bool, 22 | pub vsock_port: u32, 23 | pub tcp_port: u32, 24 | pub timeout: u32, 25 | pub openid_user: Option, 26 | pub https_proxy: Option, 27 | } 28 | 29 | impl Config { 30 | pub fn new() -> Config { 31 | Config { 32 | vsock_conn: false, 33 | vsock_port: DEF_VSOCK_PORT, 34 | tcp_port: DEF_TCP_PORT, 35 | timeout: DEF_TIMEOUT, 36 | openid_user: None, 37 | https_proxy: None, 38 | } 39 | } 40 | 41 | pub fn parse_cmdline(&mut self, file: Option<&str>) -> Result<()> { 42 | let f = file.unwrap_or(KERNEL_CMDLINE); 43 | let cmdline = fs::read_to_string(f)?; 44 | let params = cmdline.split_ascii_whitespace(); 45 | 46 | for param in params { 47 | let mut parts = param.splitn(2, '='); 48 | let key = parts.next(); 49 | let value = parts.next(); 50 | 51 | match key { 52 | Some(KEY_VSOCK_CONN) if value.is_none() => self.vsock_conn = true, 53 | Some(KEY_VSOCK_PORT) => { 54 | self.vsock_port = value 55 | .ok_or_else(|| anyhow!(utils::ERR_CFG_INVALID_VSOCK_PORT))? 56 | .parse::() 57 | .map_err(|_| anyhow!(utils::ERR_CFG_INVALID_VSOCK_PORT))? 58 | } 59 | Some(KEY_TCP_PORT) => { 60 | self.tcp_port = value 61 | .ok_or_else(|| anyhow!(utils::ERR_CFG_INVALID_TCPIP_PORT))? 62 | .parse::() 63 | .map_err(|_| anyhow!(utils::ERR_CFG_INVALID_TCPIP_PORT))? 64 | } 65 | Some(KEY_TIMEOUT) => { 66 | self.timeout = value 67 | .ok_or_else(|| anyhow!(utils::ERR_CFG_INVALID_TIMEOUT))? 68 | .parse::() 69 | .map_err(|_| anyhow!(utils::ERR_CFG_INVALID_TIMEOUT))? 70 | } 71 | Some(KEY_OPENID_USER) => self.openid_user = value.map(|s| s.into()), 72 | Some(KEY_HTTPS_PROXY) => self.https_proxy = value.map(|s| s.into()), 73 | _ => (), 74 | } 75 | } 76 | 77 | Ok(()) 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod unit_test { 83 | use super::*; 84 | use std::fs::File; 85 | use std::io::Write; 86 | 87 | const VAL_VSOCK_PORT: u32 = 8888; 88 | const VAL_TIMEOUT: u32 = 100; 89 | 90 | #[test] 91 | fn test_new() { 92 | let conf = Config::new(); 93 | assert_eq!(conf.vsock_port, DEF_VSOCK_PORT); 94 | assert_eq!(conf.timeout, DEF_TIMEOUT); 95 | } 96 | 97 | #[test] 98 | fn test_parse_cmdline() { 99 | let tmpdir = tempfile::tempdir().unwrap(); 100 | let tmpfile = tmpdir.path().join("cmdline"); 101 | 102 | { 103 | // No parameters 104 | File::create(&tmpfile).unwrap(); 105 | 106 | let mut conf = Config::new(); 107 | conf.parse_cmdline(tmpfile.to_str()).unwrap(); 108 | assert_eq!(conf.vsock_port, DEF_VSOCK_PORT); 109 | assert_eq!(conf.timeout, DEF_TIMEOUT); 110 | } 111 | 112 | { 113 | // Only include vsock port number in the parameters 114 | let mut file = File::create(&tmpfile).unwrap(); 115 | write!(file, "{}={}", KEY_VSOCK_PORT, VAL_VSOCK_PORT).unwrap(); 116 | 117 | let mut conf = Config::new(); 118 | conf.parse_cmdline(tmpfile.to_str()).unwrap(); 119 | assert_eq!(conf.vsock_port, VAL_VSOCK_PORT); 120 | assert_eq!(conf.timeout, DEF_TIMEOUT); 121 | } 122 | 123 | { 124 | // Only include timeout in the parameters 125 | let mut file = File::create(&tmpfile).unwrap(); 126 | write!(file, "{}={}", KEY_TIMEOUT, VAL_TIMEOUT).unwrap(); 127 | 128 | let mut conf = Config::new(); 129 | conf.parse_cmdline(tmpfile.to_str()).unwrap(); 130 | assert_eq!(conf.vsock_port, DEF_VSOCK_PORT); 131 | assert_eq!(conf.timeout, VAL_TIMEOUT); 132 | } 133 | 134 | { 135 | // Include vsock port number/timeout in the parameters 136 | let mut file = File::create(&tmpfile).unwrap(); 137 | write!( 138 | file, 139 | "{}={} {}={}", 140 | KEY_VSOCK_PORT, VAL_VSOCK_PORT, KEY_TIMEOUT, VAL_TIMEOUT 141 | ) 142 | .unwrap(); 143 | 144 | let mut conf = Config::new(); 145 | conf.parse_cmdline(tmpfile.to_str()).unwrap(); 146 | assert_eq!(conf.vsock_port, VAL_VSOCK_PORT); 147 | assert_eq!(conf.timeout, VAL_TIMEOUT); 148 | } 149 | 150 | { 151 | // Invalid parameters - vsock port number 152 | let mut file = File::create(&tmpfile).unwrap(); 153 | write!(file, "{}={}=1", KEY_VSOCK_PORT, VAL_VSOCK_PORT).unwrap(); 154 | 155 | let mut conf = Config::new(); 156 | let ret = conf.parse_cmdline(tmpfile.to_str()); 157 | assert!(ret.is_err()); 158 | } 159 | 160 | { 161 | // Invalid parameters - vsock port number 162 | let mut file = File::create(&tmpfile).unwrap(); 163 | write!(file, "{}=xxx", KEY_VSOCK_PORT).unwrap(); 164 | 165 | let mut conf = Config::new(); 166 | let ret = conf.parse_cmdline(tmpfile.to_str()); 167 | assert!(ret.is_err()); 168 | } 169 | 170 | { 171 | // Invalid parameters - timeout 172 | let mut file = File::create(&tmpfile).unwrap(); 173 | write!(file, "{}={}=", KEY_TIMEOUT, VAL_TIMEOUT).unwrap(); 174 | 175 | let mut conf = Config::new(); 176 | let ret = conf.parse_cmdline(tmpfile.to_str()); 177 | assert!(ret.is_err()); 178 | } 179 | 180 | { 181 | // Invalid parameters - timeout 182 | let mut file = File::create(&tmpfile).unwrap(); 183 | write!(file, "{}=xxx", KEY_TIMEOUT).unwrap(); 184 | 185 | let mut conf = Config::new(); 186 | let ret = conf.parse_cmdline(tmpfile.to_str()); 187 | assert!(ret.is_err()); 188 | } 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /acond/proto/acon.proto: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2023 Intel Corporation 2 | // SPDX-License-Identifier: Apache-2.0 3 | 4 | syntax = "proto3"; 5 | import "google/protobuf/empty.proto"; 6 | 7 | package acon.grpc; 8 | 9 | message AddManifestRequest { 10 | string manifest = 1; 11 | bytes signature = 2; 12 | bytes certificate = 3; 13 | } 14 | 15 | message AddManifestResponse { 16 | string image_id = 1; 17 | repeated string missing_layers = 2; 18 | } 19 | 20 | message AddBlobRequest { 21 | // 00000001 sha256 22 | // 00000010 sha384 23 | // 00000100 sha512 24 | uint32 alg = 1; 25 | bytes data = 2; 26 | } 27 | 28 | message StartRequest { 29 | string image_id = 1; 30 | repeated string envs = 2; 31 | } 32 | 33 | message StartResponse { 34 | uint32 container_id = 1; 35 | } 36 | 37 | message RestartRequest { 38 | uint32 container_id = 1; 39 | uint64 timeout = 2; 40 | } 41 | 42 | message ExecRequest { 43 | uint32 container_id = 1; 44 | string command = 2; 45 | uint64 timeout = 3; 46 | repeated string arguments = 4; 47 | repeated string envs = 5; 48 | bytes stdin = 6; 49 | uint64 capture_size = 7; 50 | } 51 | 52 | message ExecResponse { 53 | bytes stdout = 1; 54 | bytes stderr = 2; 55 | } 56 | 57 | message KillRequest { 58 | uint32 container_id = 1; 59 | int32 signal_num = 2; 60 | } 61 | 62 | message InspectRequest { 63 | uint32 container_id = 1; 64 | } 65 | 66 | message InspectResponse { 67 | repeated ContainerInfo info = 1; 68 | } 69 | 70 | message ContainerInfo { 71 | uint32 container_id = 1; 72 | uint32 state = 2; 73 | int32 wstatus = 3; 74 | string image_id = 4; 75 | string exe_path = 5; 76 | } 77 | 78 | message ReportRequest { 79 | uint64 nonce_lo = 1; 80 | uint64 nonce_hi = 2; 81 | uint32 request_type = 3; // 0 is report and 1 is quote 82 | } 83 | 84 | message MrLog { 85 | repeated string logs = 1; 86 | } 87 | 88 | message ReportResponse { 89 | bytes data = 1; 90 | map mrlog = 2; 91 | string attestation_data = 3; 92 | } 93 | 94 | message GetManifestRequest { 95 | string image_id = 1; 96 | } 97 | 98 | message GetManifestResponse { 99 | string manifest = 1; 100 | bytes certificate = 2; 101 | } 102 | 103 | service AconService { 104 | // Accepts and measures a Manifest. 105 | // 106 | // AddManifest determines the acceptability of the supplied Manifest, and if accepted, 107 | // measures and then stores the manifest. 108 | // 109 | // A Manifest must meet the following requirements to be accepted into a TD. 110 | // 1. Must be signed properly. 111 | // 2. Must not reject any existing Images. 112 | // 3. Must not be rejected by any existing Images. 113 | // 114 | // On success, returns the hash value of the manifest and the missing layers. 115 | // On failure, returns the specified error. 116 | rpc AddManifest(AddManifestRequest) returns (AddManifestResponse); 117 | 118 | // Not receive any manifests after finalization. 119 | // 120 | // On success, returns OK. 121 | // On failure, returns the specified error. 122 | rpc Finalize(google.protobuf.Empty) returns (google.protobuf.Empty); 123 | 124 | 125 | // Loads a blob (containing a FS layer) into the TD. 126 | // 127 | // AddBlob loads an arbitrary blob into the TD. In the current spec, blobs must be in 128 | // (uncompressed) TAR format. 129 | // 130 | // On success, returns OK. 131 | // On failure, returns the specified error. 132 | rpc AddBlob(AddBlobRequest) returns (google.protobuf.Empty); 133 | 134 | 135 | // Starts a new container of an Image. 136 | // 137 | // Start creates a new Container of the Image specified by image_id, which is usually 138 | // returned by AddManifest. 139 | // 140 | // On success, returns the unique identifier of the new Container. 141 | // On failure, returns the specified error. 142 | rpc Start(StartRequest) returns (StartResponse); 143 | 144 | // Restarts an existing container. 145 | // 146 | // Restarts starts an exited Container or restarts a running Conainer specified by 147 | // container_id, which is usually returned by Start. 148 | // If the value of noRestart in the corresponding manifest is true, the container 149 | // can't be restarted. 150 | // 151 | // On success, returns OK. 152 | // On failure, returns the specified error. 153 | rpc Restart(RestartRequest) returns (google.protobuf.Empty); 154 | 155 | // Executes a public entrypoint in an existing Container. 156 | // 157 | // When timeout is non-zero, Exec executes the entrypoint in batch mode. It feeds the 158 | // entrypoint's stdin with the bytes passed in via stdin (of ExecRequest), and captures 159 | // stdout and stderr into stdout and stderr (of ExecResponse), respectively. timeout is 160 | // the duration (in seconds) for which Exec should wait - i.e., Exec returns when the 161 | // entrypoint process terminates or after timeout has elapsed, depending on which comes first. 162 | // In the case the entrypoint cannot terminate with timeout, it will continue running, but 163 | // further outputs to stdout and stderr will be discarded. 164 | // 165 | // When timeout is 0 (zero), Exec executes the entrypoint in interactive mode by allocating 166 | // and attaching a new pseudo-terminal to the entrypoint process. In this mode, Exec returns 167 | // immediately after the entrypoint process has started. stdin (of ExecRequest) is ignored, 168 | // while stdout and stderr (of ExecResponse) are not used. If the TD was launched in interactive 169 | // mode, the user can bring the pseudo-terminal to foreground for interacting with the entrypoint 170 | // process. 171 | // 172 | // On failure, returns the specified error. 173 | rpc Exec(ExecRequest) returns (ExecResponse); 174 | 175 | // Sends signal which must exist in the corresponding manifest to an existing container. 176 | // 177 | // container_id specifies the Container to which the specified sinal will be sent. The signal must 178 | // exist in the corresponding manifest. Otherwise, it fails with errors. 179 | // 180 | // On success, returns OK. 181 | // On failure, returns the specified error. 182 | rpc Kill(KillRequest) returns (google.protobuf.Empty); 183 | 184 | // Retrieves information of the specified Containers. 185 | // 186 | // container_id specifies the Container whose status to be retrieved. If container_id is 0 (zero), 187 | // the status of all Container's will be returned. 188 | // 189 | // On success, returns an array of ContainerInfo entries. 190 | // On failure, returns the specified error. 191 | rpc Inspect(InspectRequest) returns (InspectResponse); 192 | 193 | // Returns a TDREPORT, along with measurement logs and additional attestation data attached by 194 | // Containers. 195 | // 196 | // On input, nonce_lo and nonce_hi allow the caller to associate a 128-bit nonce to the report 197 | // for replay protection. 198 | // On output, report contains the TDREPORT. Its 64-byte REPORTDATA field is constructed as 199 | // ACOND_NONCE || sha384(ACOND_NONCE || REQUESTOR_NONCE || attestation_data) 200 | // 201 | // On success, returns a TDREPORT. 202 | // On error, returns the specified error. 203 | rpc Report(ReportRequest) returns (ReportResponse); 204 | 205 | // Retrieves the specified manifest. 206 | // 207 | // Returns the manifest and its signing certificate for the Image identified by image_id. 208 | // 209 | // On success, returns the manifest and its signing certificate. 210 | // On error, returns the specified error. 211 | rpc GetManifest(GetManifestRequest) returns (GetManifestResponse); 212 | } 213 | --------------------------------------------------------------------------------