зеркало из https://github.com/microsoft/CCF.git
Initial attestation container implementation (#4694)
Co-authored-by: Eddy Ashton <ashton.eddy@gmail.com> Co-authored-by: Julien Maffre <42961061+jumaffre@users.noreply.github.com>
This commit is contained in:
Родитель
18963ab2b7
Коммит
5ee261b755
|
@ -1 +1,2 @@
|
|||
<><><>
|
||||
<>??<><><><>s
|
||||
<
|
|
@ -4,20 +4,27 @@ pr:
|
|||
- main
|
||||
paths:
|
||||
include:
|
||||
- .azure_pipelines_attestation_container.yml
|
||||
- .attestation_container_canary
|
||||
- attestation-container/*
|
||||
- .azure_pipelines_attestation_container.yml
|
||||
- .azure-pipelines-templates/deploy_attestation_container.yml
|
||||
- .azure-pipelines-templates/test_attestation_container.yml
|
||||
|
||||
trigger:
|
||||
- main
|
||||
|
||||
pool:
|
||||
vmImage: ubuntu-latest
|
||||
jobs:
|
||||
- template: .azure-pipelines-templates/configure.yml
|
||||
|
||||
steps:
|
||||
- script: echo Hello, world!
|
||||
displayName: "Run a one-line script"
|
||||
- template: .azure-pipelines-templates/deploy_attestation_container.yml
|
||||
parameters:
|
||||
used_by:
|
||||
- test_attestation_container
|
||||
|
||||
- script: |
|
||||
echo Add other tasks to build, test, and deploy your project.
|
||||
echo See https://aka.ms/yaml
|
||||
displayName: "Run a multi-line script"
|
||||
- template: .azure-pipelines-templates/test_attestation_container.yml
|
||||
parameters:
|
||||
job_name: test_attestation_container
|
||||
display_name: "Test Attestation Container"
|
||||
depends_on: deploy_attestation_container
|
||||
run_on: $[ dependencies.deploy_attestation_container.outputs['deploy_attestation_container.ipAddresses'] ]
|
||||
host_private_key: $[ dependencies.deploy_attestation_container.outputs['generate_ssh_key.hostPrivKey'] ]
|
||||
|
|
|
@ -2,11 +2,7 @@ steps:
|
|||
- script: |
|
||||
set -ex
|
||||
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
|
||||
az login --service-principal -u $(CCF_SNP_CI_APP_ID) -p $(CCF_SNP_CI_SERVICE_PRINCIPAL_PASSWORD) --tenant $(CCF_SNP_CI_TENANT)
|
||||
az login --service-principal -u ${{ parameters.app_id }} -p ${{ parameters.service_principal_password }} --tenant ${{ parameters.tenant }}
|
||||
pip install azure-mgmt-resource azure-identity azure-mgmt-containerinstance
|
||||
name: setup_azure_cli
|
||||
displayName: "Install Azure CLI, login, and install Python Packages"
|
||||
env:
|
||||
CCF_SNP_CI_APP_ID: $(CCF_SNP_CI_APP_ID)
|
||||
CCF_SNP_CI_SERVICE_PRINCIPAL_PASSWORD: $(CCF_SNP_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
CCF_SNP_CI_TENANT: $(CCF_SNP_CI_TENANT)
|
||||
|
|
|
@ -20,6 +20,10 @@ jobs:
|
|||
displayName: "Generate SSH Key"
|
||||
|
||||
- template: azure_cli.yml
|
||||
parameters:
|
||||
app_id: $(CCF_SNP_CI_APP_ID)
|
||||
service_principal_password: $(CCF_SNP_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
tenant: $(CCF_SNP_CI_TENANT)
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
|
@ -44,7 +48,16 @@ jobs:
|
|||
- script: |
|
||||
set -ex
|
||||
python3.8 scripts/azure_deployment/arm_template.py deploy aci --subscription-id $(CCF_AZURE_SUBSCRIPTION_ID) --resource-group ccf-aci --aci-type dynamic-agent --deployment-name ci-$(Build.BuildNumber) --aci-image ccfmsrc.azurecr.io/ccf/ci:pr-$(wait_for_image.gitSha) --aci-storage-account-key $(CCF_AZURE_STORAGE_KEY) > ~/aci_ips
|
||||
echo "##vso[task.setvariable variable=ipAddresses;isOutput=true]`cat ~/aci_ips`"
|
||||
# Escape newlines: https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash
|
||||
escape_data() {
|
||||
local data=$1
|
||||
data="${data//'%'/'%AZP25'}"
|
||||
data="${data//$'\n'/'%0A'}"
|
||||
data="${data//$'\r'/'%0D'}"
|
||||
echo "$data"
|
||||
}
|
||||
# Set a variable "ipAddresses" which is a list of `<container group name> <IP address>` separated by newlines.
|
||||
echo "##vso[task.setvariable variable=ipAddresses;isOutput=true]$(escape_data "$(cat ~/aci_ips)")"
|
||||
name: deploy_aci
|
||||
displayName: "Deploy ACI"
|
||||
env:
|
||||
|
@ -62,10 +75,14 @@ jobs:
|
|||
IpAddresses: $[ dependencies.deploy_aci.outputs['deploy_aci.ipAddresses'] ]
|
||||
steps:
|
||||
- template: azure_cli.yml
|
||||
parameters:
|
||||
app_id: $(CCF_SNP_CI_APP_ID)
|
||||
service_principal_password: $(CCF_SNP_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
tenant: $(CCF_SNP_CI_TENANT)
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
IFS='\n' read -ra IP_ADDR_LIST <<< "$(IpAddresses)"
|
||||
mapfile -t IP_ADDR_LIST <<< $(echo "$(IpAddresses)" | awk '{print $2}')
|
||||
for IP_ADDR in "${IP_ADDR_LIST[@]}"; do
|
||||
rm -rf /ccfci/workspace_$(Build.BuildNumber)
|
||||
done
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
jobs:
|
||||
- job: deploy_attestation_container
|
||||
displayName: "Deploy Attestation Container"
|
||||
variables:
|
||||
Codeql.SkipTaskAutoInjection: true
|
||||
skipComponentGovernanceDetection: true
|
||||
pool:
|
||||
vmImage: ubuntu-20.04
|
||||
steps:
|
||||
- script: |
|
||||
set -ex
|
||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
|
||||
echo "##vso[task.setvariable variable=hostPrivKey;isOutput=true;issecret=true]`base64 -w 0 ~/.ssh/id_rsa`"
|
||||
name: generate_ssh_key
|
||||
displayName: "Generate SSH Key"
|
||||
|
||||
- template: azure_cli.yml
|
||||
parameters:
|
||||
app_id: $(ATTESTATION_CONTAINER_CI_APP_ID)
|
||||
service_principal_password: $(ATTESTATION_CONTAINER_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
tenant: $(ATTESTATION_CONTAINER_CI_TENANT)
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
cd attestation-container
|
||||
docker build -t attestation-container .
|
||||
az acr login --name attestationcontainerregistry
|
||||
login_server=$(az acr show --name attestationcontainerregistry --query loginServer --output tsv)
|
||||
image_version="ci-$(echo $(Build.BuildNumber) | sed 's/\.//g')"
|
||||
docker tag attestation-container $login_server/attestation-container:$image_version
|
||||
docker push $login_server/attestation-container:$image_version
|
||||
echo "##vso[task.setvariable variable=ver;isoutput=true]$image_version"
|
||||
name: build_and_push_attestation_container
|
||||
displayName: "Build and Push Attestation Container"
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
python3.8 scripts/azure_deployment/arm_template.py deploy aci --subscription-id $(CCF_AZURE_SUBSCRIPTION_ID) --resource-group attestationContainer --aci-type dynamic-agent --deployment-name ci-$(Build.BuildNumber) --attestation-container-e2e True > ~/aci_ips
|
||||
# Escape newlines: https://learn.microsoft.com/en-us/azure/devops/pipelines/process/set-variables-scripts?view=azure-devops&tabs=bash
|
||||
escape_data() {
|
||||
local data=$1
|
||||
data="${data//'%'/'%AZP25'}"
|
||||
data="${data//$'\n'/'%0A'}"
|
||||
data="${data//$'\r'/'%0D'}"
|
||||
echo "$data"
|
||||
}
|
||||
# Set a variable "ipAddresses" which is a list of `<container group name> <IP address>` separated by newlines.
|
||||
echo "##vso[task.setvariable variable=ipAddresses;isOutput=true]$(escape_data "$(cat ~/aci_ips)")"
|
||||
name: deploy_attestation_container
|
||||
displayName: "Deploy Attestation Container"
|
||||
env:
|
||||
CCF_AZURE_SUBSCRIPTION_ID: $(CCF_AZURE_SUBSCRIPTION_ID)
|
||||
|
||||
- job: cleanup_aci
|
||||
displayName: "Cleanup Attestation Container"
|
||||
pool:
|
||||
vmImage: ubuntu-20.04
|
||||
dependsOn: ${{ parameters.used_by }}
|
||||
condition: always()
|
||||
steps:
|
||||
- template: azure_cli.yml
|
||||
parameters:
|
||||
app_id: $(ATTESTATION_CONTAINER_CI_APP_ID)
|
||||
service_principal_password: $(ATTESTATION_CONTAINER_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
tenant: $(ATTESTATION_CONTAINER_CI_TENANT)
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
python3.8 scripts/azure_deployment/arm_template.py remove aci --subscription-id $(CCF_AZURE_SUBSCRIPTION_ID) --resource-group attestationContainer --aci-type dynamic-agent --deployment-name ci-$(Build.BuildNumber)
|
||||
name: cleanup_aci
|
||||
displayName: "Delete the ACI and Azure Deployment"
|
||||
|
||||
- job: cleanup_container_image
|
||||
displayName: "Cleanup Attestation Container image"
|
||||
pool:
|
||||
vmImage: ubuntu-20.04
|
||||
dependsOn:
|
||||
- deploy_attestation_container
|
||||
- ${{ parameters.used_by }}
|
||||
variables:
|
||||
image_version: $[ dependencies.deploy_attestation_container.outputs['build_and_push_attestation_container.ver'] ]
|
||||
condition: always()
|
||||
steps:
|
||||
- template: azure_cli.yml
|
||||
parameters:
|
||||
app_id: $(ATTESTATION_CONTAINER_CI_APP_ID)
|
||||
service_principal_password: $(ATTESTATION_CONTAINER_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
tenant: $(ATTESTATION_CONTAINER_CI_TENANT)
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
az acr repository delete --name attestationcontainerregistry --image attestation-container:$image_version --yes
|
||||
|
||||
name: cleanup_container_image
|
||||
displayName: "Delete the container image"
|
|
@ -0,0 +1,67 @@
|
|||
parameters:
|
||||
depends_on: ""
|
||||
condition: ""
|
||||
|
||||
jobs:
|
||||
- job: ${{ parameters.job_name }}
|
||||
displayName: ${{ parameters.display_name }}
|
||||
dependsOn: ${{ parameters.depends_on }}
|
||||
condition: ${{ parameters.condition }}
|
||||
pool:
|
||||
vmImage: ubuntu-20.04
|
||||
timeoutInMinutes: 120
|
||||
variables:
|
||||
RUN_ON: ${{ parameters.run_on }}
|
||||
HOST_PRIVATE_KEY: ${{ parameters.host_private_key }}
|
||||
Codeql.SkipTaskAutoInjection: true
|
||||
skipComponentGovernanceDetection: true
|
||||
|
||||
steps:
|
||||
- script: |
|
||||
set -ex
|
||||
mkdir ~/.ssh
|
||||
echo $(HOST_PRIVATE_KEY) | base64 -d > ~/.ssh/id_rsa
|
||||
sudo chmod 600 ~/.ssh/id_rsa
|
||||
name: setup_key
|
||||
displayName: "Install SSH Key from Deployment Step"
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
# The following sed is to change 20221213.48 to 2022121348 for example
|
||||
echo "##vso[task.setvariable variable=BUILD_NUMBER]$(echo $(Build.BuildNumber) | sed 's/\.//g')"
|
||||
name: set_build_number_var
|
||||
displayName: "Set BUILD_NUMBER variable"
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
echo "##vso[task.setvariable variable=IP_ADDR]$(echo "$(RUN_ON)" | awk -v container_group="ci-$(BUILD_NUMBER)-business-logic" '$1 == container_group {print $2}')"
|
||||
name: set_ip_addr_var
|
||||
displayName: "Set IP_ADDR variable"
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
echo -e "Testing connection with $IP_ADDR"
|
||||
ssh agent@$IP_ADDR -o "StrictHostKeyChecking no" 'echo "Connected successfully for port 22" && cd /usr/src/app/attest && /usr/local/go/bin/go test'
|
||||
name: run_unit_test
|
||||
displayName: "Unit Test in Attestation Container Instance Deployed ACIs"
|
||||
|
||||
- task: GoTool@0
|
||||
inputs:
|
||||
version: "1.19.3"
|
||||
|
||||
- template: azure_cli.yml
|
||||
parameters:
|
||||
app_id: $(ATTESTATION_CONTAINER_CI_APP_ID)
|
||||
service_principal_password: $(ATTESTATION_CONTAINER_CI_SERVICE_PRINCIPAL_PASSWORD)
|
||||
tenant: $(ATTESTATION_CONTAINER_CI_TENANT)
|
||||
|
||||
- script: |
|
||||
set -ex
|
||||
az account set --subscription $(CCF_AZURE_SUBSCRIPTION_ID)
|
||||
echo "Testing attestation container..."
|
||||
cd attestation-container
|
||||
echo "testing against ${IP_ADDR} ..."
|
||||
go test . --addr $IP_ADDR:50051 -v
|
||||
az container logs --resource-group attestationContainer --name ci-$(BUILD_NUMBER)-business-logic --container-name ci-$(BUILD_NUMBER)-attestation-container
|
||||
name: test_attestation_container
|
||||
displayName: "Test attestation container"
|
|
@ -27,7 +27,7 @@ jobs:
|
|||
|
||||
- script: |
|
||||
set -ex
|
||||
IFS='\n' read -ra IP_ADDR_LIST <<< "$(RUN_ON)"
|
||||
mapfile -t IP_ADDR_LIST <<< $(echo "$(RUN_ON)" | awk '{print $2}')
|
||||
ssh agent@${IP_ADDR_LIST[0]} -o "StrictHostKeyChecking no" '
|
||||
cd /CCF/build
|
||||
npm config set cache /ccfci/workspace_$(Build.BuildNumber)/.npm
|
||||
|
@ -38,7 +38,7 @@ jobs:
|
|||
|
||||
- script: |
|
||||
set -ex
|
||||
IFS='\n' read -ra IP_ADDR_LIST <<< "$(RUN_ON)"
|
||||
mapfile -t IP_ADDR_LIST <<< $(echo "$(RUN_ON)" | awk '{print $2}')
|
||||
ssh agent@${IP_ADDR_LIST[0]} -o "StrictHostKeyChecking no" '
|
||||
dmesg
|
||||
'
|
||||
|
|
|
@ -14,3 +14,8 @@ updates:
|
|||
directory: "/python" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
||||
- package-ecosystem: "gomod"
|
||||
directory: "/attestation-container" # Location of package manifests
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
attestation-container
|
|
@ -0,0 +1,18 @@
|
|||
# Based on https://hub.docker.com/_/golang/
|
||||
|
||||
# To run this:
|
||||
# docker build -t attestation-container .
|
||||
# docker run -it --rm -p 50051:50051 attestation-container
|
||||
|
||||
FROM golang:1.19
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# pre-copy/cache go.mod for pre-downloading dependencies and only redownloading them in subsequent builds if they change
|
||||
COPY go.mod go.sum ./
|
||||
RUN go mod download && go mod verify
|
||||
|
||||
COPY . .
|
||||
RUN go build -v -o /usr/local/bin/app .
|
||||
|
||||
CMD ["app"]
|
|
@ -0,0 +1,48 @@
|
|||
# Attestation Container
|
||||
|
||||
This is a gRPC server application to fetch SEV-SNP attestation and collateral.
|
||||
|
||||
## Environment
|
||||
|
||||
This application needs to run on [SEV-SNP VM](https://www.amd.com/system/files/TechDocs/SEV-SNP-strengthening-vm-isolation-with-integrity-protection-and-more.pdf).
|
||||
|
||||
## Dependencies
|
||||
|
||||
- [Go](https://go.dev/doc/install)
|
||||
- [gRPC](https://grpc.io/docs/languages/go/quickstart/)
|
||||
|
||||
## How to start the app
|
||||
|
||||
The following command starts the gRPC server application.
|
||||
|
||||
```bash
|
||||
# In the same directory as this README.md
|
||||
go run .
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
Unit test:
|
||||
|
||||
```bash
|
||||
cd attest
|
||||
go test
|
||||
```
|
||||
|
||||
E2E test:
|
||||
|
||||
```bash
|
||||
# Run the app first
|
||||
go run .
|
||||
|
||||
# In another terminal
|
||||
go test
|
||||
```
|
||||
|
||||
## Update protobuf
|
||||
|
||||
When you edit `.proto` file, you also need to update `.pb.go` files by:
|
||||
|
||||
```bash
|
||||
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative protobuf/attestation-container.proto
|
||||
```
|
|
@ -0,0 +1,148 @@
|
|||
package attest
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
// Data structures are based on SEV-SNP Firmware ABI Specification
|
||||
// https://www.amd.com/en/support/tech-docs/sev-secure-nested-paging-firmware-abi-specification
|
||||
|
||||
const (
|
||||
ATTESTATION_REPORT_SIZE = 1184 // Size of ATTESTATION_REPORT (Table 21)
|
||||
REPORT_DATA_SIZE = 64 // Size of REPORT_DATA_SIZE in ATTESTATION_REPORT
|
||||
REPORT_REQ_SIZE = 96 // Size of MSG_REPORT_REQ (Table 20)
|
||||
REPORT_RSP_SIZE = 1280 // Size of MSG_REPORT_RSP (Table 23)
|
||||
PAYLOAD_SIZE = 40 // Size of sev_snp_guest_request struct from sev-snp driver include/uapi/linux/psp-sev-guest.h
|
||||
)
|
||||
|
||||
// Message Type Encodings (Table 100)
|
||||
const (
|
||||
MSG_REPORT_REQ = 5
|
||||
MSG_REPORT_RSP = 6
|
||||
)
|
||||
|
||||
// From sev-snp driver include/uapi/linux/psp-sev-guest.h
|
||||
const SEV_SNP_GUEST_MSG_REPORT = 3223868161
|
||||
|
||||
const SNP_DEVICE_PATH = "/dev/sev"
|
||||
|
||||
/*
|
||||
Creates and returns MSG_REPORT_REQ message bytes (SEV-SNP Firmware ABI Specification Table 20)
|
||||
*/
|
||||
func createReportReqBytes(reportData [REPORT_DATA_SIZE]byte) [REPORT_REQ_SIZE]byte {
|
||||
reportReqBytes := [REPORT_REQ_SIZE]byte{}
|
||||
copy(reportReqBytes[0:REPORT_DATA_SIZE], reportData[:])
|
||||
return reportReqBytes
|
||||
}
|
||||
|
||||
/*
|
||||
Creates and returns byte array of the following C struct
|
||||
|
||||
// From sev-snp driver include/uapi/linux/psp-sev-guest.h
|
||||
// struct sev_snp_guest_request {
|
||||
// uint8_t req_msg_type;
|
||||
// uint8_t rsp_msg_type;
|
||||
// uint8_t msg_version;
|
||||
// uint16_t request_len;
|
||||
// uint64_t request_uaddr;
|
||||
// uint16_t response_len;
|
||||
// uint64_t response_uaddr;
|
||||
// uint32_t error; // firmware error code on failure (see psp-sev.h)
|
||||
// };
|
||||
|
||||
The padding is based on Section 3.1.2 of System V ABI for AMD64
|
||||
https://www.uclibc.org/docs/psABI-x86_64.pdf
|
||||
*/
|
||||
func createPayloadBytes(reportReqPtr uintptr, ReportRespPtr uintptr) ([PAYLOAD_SIZE]byte, error) {
|
||||
payload := [PAYLOAD_SIZE]byte{}
|
||||
var buf bytes.Buffer
|
||||
// req_msg_type
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint8(MSG_REPORT_REQ)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// rsp_msg_type
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint8(MSG_REPORT_RSP)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// msg_version
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint8(1)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// Padding
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint8(0)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// request_len
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint16(REPORT_REQ_SIZE)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// Padding
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint16(0)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// request_uaddr
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint64(reportReqPtr)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// response_len
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint16(REPORT_RSP_SIZE)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// Padding
|
||||
if err := binary.Write(&buf, binary.LittleEndian, [3]uint16{}); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// response_uaddr
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint64(ReportRespPtr)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// error
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint32(0)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
// Padding
|
||||
if err := binary.Write(&buf, binary.LittleEndian, uint32(0)); err != nil {
|
||||
return payload, err
|
||||
}
|
||||
for i, x := range buf.Bytes() {
|
||||
payload[i] = x
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func FetchAttestationReportByte(reportData [64]byte) ([]byte, error) {
|
||||
fd, err := unix.Open(SNP_DEVICE_PATH, unix.O_RDWR|unix.O_CLOEXEC, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
reportReqBytes := createReportReqBytes(reportData)
|
||||
// MSG_REPORT_RSP message bytes (SEV-SNP Firmware Firmware ABI Specification Table 23)
|
||||
reportRspBytes := [REPORT_RSP_SIZE]byte{}
|
||||
payload, err := createPayloadBytes(uintptr(unsafe.Pointer(&reportReqBytes[0])), uintptr(unsafe.Pointer(&reportRspBytes[0])))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, errno := unix.Syscall(
|
||||
unix.SYS_IOCTL,
|
||||
uintptr(fd),
|
||||
uintptr(SEV_SNP_GUEST_MSG_REPORT),
|
||||
uintptr(unsafe.Pointer(&payload[0])),
|
||||
)
|
||||
|
||||
if errno != 0 {
|
||||
return nil, fmt.Errorf("ioctl failed:%v", errno)
|
||||
}
|
||||
|
||||
if status := binary.LittleEndian.Uint32(reportRspBytes[0:4]); status != 0 {
|
||||
return nil, fmt.Errorf("fetching attestation report failed. status: %v", status)
|
||||
}
|
||||
const SNP_REPORT_OFFSET = 32
|
||||
return reportRspBytes[SNP_REPORT_OFFSET : SNP_REPORT_OFFSET+ATTESTATION_REPORT_SIZE], nil
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package attest
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertEqual[T comparable](t *testing.T, description string, expect T, actual T) {
|
||||
if expect != actual {
|
||||
t.Fatalf("%s: Expected %v, but got %v", description, expect, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestFetchReport(t *testing.T) {
|
||||
// Report data for test
|
||||
reportData := [REPORT_DATA_SIZE]byte{}
|
||||
for i := 0; i < REPORT_DATA_SIZE; i++ {
|
||||
reportData[i] = byte(i)
|
||||
}
|
||||
|
||||
reportBytes, err := FetchAttestationReportByte(reportData)
|
||||
if err != nil {
|
||||
t.Fatalf("Fetching report failed: %v", err)
|
||||
}
|
||||
expectedByteString := hex.EncodeToString(reportData[:])
|
||||
// Confirm `report data` (user provided 64 byte data) is correct
|
||||
// Offset of `report data` is specified in SEV-SNP Firmware ABI Specification Table 21
|
||||
// https://www.amd.com/en/support/tech-docs/sev-secure-nested-paging-firmware-abi-specification
|
||||
const REPORT_DATA_OFFSET = 80
|
||||
assertEqual(t, "Check report data", expectedByteString, hex.EncodeToString(reportBytes[REPORT_DATA_OFFSET:REPORT_DATA_OFFSET+REPORT_DATA_SIZE]))
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"microsoft/attestation-container/attest"
|
||||
pb "microsoft/attestation-container/protobuf"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
var (
|
||||
port = flag.Int("port", 50051, "The server port")
|
||||
)
|
||||
|
||||
type server struct {
|
||||
pb.UnimplementedAttestationContainerServer
|
||||
}
|
||||
|
||||
func (s *server) FetchAttestation(ctx context.Context, in *pb.FetchAttestationRequest) (*pb.FetchAttestationReply, error) {
|
||||
reportData := [attest.REPORT_DATA_SIZE]byte{}
|
||||
if len(in.GetReportData()) > attest.REPORT_DATA_SIZE {
|
||||
return nil, fmt.Errorf("`report_data` needs to be smaller than %d bytes. size: %d bytes", attest.REPORT_DATA_SIZE, len(in.GetReportData()))
|
||||
}
|
||||
copy(reportData[:], in.GetReportData())
|
||||
reportBytes, err := attest.FetchAttestationReportByte(reportData)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch attestation report: %s", err)
|
||||
}
|
||||
return &pb.FetchAttestationReply{Attestation: reportBytes}, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Println("Attestation container started.")
|
||||
|
||||
if _, err := os.Stat(attest.SNP_DEVICE_PATH); err == nil {
|
||||
fmt.Printf("%s is detected", attest.SNP_DEVICE_PATH)
|
||||
} else if errors.Is(err, os.ErrNotExist) {
|
||||
log.Fatalf("%s is not detected", attest.SNP_DEVICE_PATH)
|
||||
} else {
|
||||
log.Fatalf("Unknown error: %s", err)
|
||||
}
|
||||
|
||||
flag.Parse()
|
||||
lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
|
||||
if err != nil {
|
||||
log.Fatalf("failed to listen: %v", err)
|
||||
}
|
||||
s := grpc.NewServer()
|
||||
pb.RegisterAttestationContainerServer(s, &server{})
|
||||
log.Printf("Server listening at %v", lis.Addr())
|
||||
if err := s.Serve(lis); err != nil {
|
||||
log.Fatalf("failed to serve: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"flag"
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
pb "microsoft/attestation-container/protobuf"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials/insecure"
|
||||
)
|
||||
|
||||
var (
|
||||
addr = flag.String("addr", "localhost:50051", "the address to connect to")
|
||||
)
|
||||
|
||||
func TestFetchReport(t *testing.T) {
|
||||
flag.Parse()
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewAttestationContainerClient(conn)
|
||||
|
||||
// Contact the server and print out its response.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
// public key bytes in UTF-8 (https://go.dev/blog/strings)
|
||||
publicKey := []byte("public-key-contents")
|
||||
r, err := c.FetchAttestation(ctx, &pb.FetchAttestationRequest{ReportData: publicKey})
|
||||
if err != nil {
|
||||
log.Fatalf("could not get attestation: %v", err)
|
||||
}
|
||||
log.Printf("Attestation: %v", hex.EncodeToString(r.GetAttestation()))
|
||||
}
|
||||
|
||||
func TestInputError(t *testing.T) {
|
||||
flag.Parse()
|
||||
// Set up a connection to the server.
|
||||
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
|
||||
if err != nil {
|
||||
log.Fatalf("did not connect: %v", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
c := pb.NewAttestationContainerClient(conn)
|
||||
|
||||
// Contact the server and print out its response.
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||
defer cancel()
|
||||
publicKey := []byte("too long (longer than 64 bytes in utf-8) ------------------------")
|
||||
if _, err := c.FetchAttestation(ctx, &pb.FetchAttestationRequest{ReportData: publicKey}); err == nil {
|
||||
log.Fatalf("server should return input error for too large input")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,16 @@
|
|||
module microsoft/attestation-container
|
||||
|
||||
go 1.19
|
||||
|
||||
require (
|
||||
golang.org/x/sys v0.3.0
|
||||
google.golang.org/grpc v1.51.0
|
||||
google.golang.org/protobuf v1.28.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.5.2 // indirect
|
||||
golang.org/x/net v0.2.0 // indirect
|
||||
golang.org/x/text v0.5.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd // indirect
|
||||
)
|
|
@ -0,0 +1,21 @@
|
|||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
|
||||
golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
|
||||
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
|
||||
golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ=
|
||||
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM=
|
||||
golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd h1:OjndDrsik+Gt+e6fs45z9AxiewiKyLKYpA45W5Kpkks=
|
||||
google.golang.org/genproto v0.0.0-20221202195650-67e5cbc046fd/go.mod h1:cTsE614GARnxrLsqKREzmNYJACSWWpAWdNMwnD7c2BE=
|
||||
google.golang.org/grpc v1.51.0 h1:E1eGv1FTqoLIdnBCZufiSHgKjlqG6fKFf6pPWtMTh8U=
|
||||
google.golang.org/grpc v1.51.0/go.mod h1:wgNDFcnuBGmxLKI/qn4T+m5BtEBYXJPvibbUPsAIPww=
|
||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
|
@ -0,0 +1,222 @@
|
|||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.12.4
|
||||
// source: protobuf/attestation-container.proto
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type FetchAttestationRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
ReportData []byte `protobuf:"bytes,1,opt,name=report_data,json=reportData,proto3" json:"report_data,omitempty"`
|
||||
}
|
||||
|
||||
func (x *FetchAttestationRequest) Reset() {
|
||||
*x = FetchAttestationRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_protobuf_attestation_container_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FetchAttestationRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FetchAttestationRequest) ProtoMessage() {}
|
||||
|
||||
func (x *FetchAttestationRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_protobuf_attestation_container_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FetchAttestationRequest.ProtoReflect.Descriptor instead.
|
||||
func (*FetchAttestationRequest) Descriptor() ([]byte, []int) {
|
||||
return file_protobuf_attestation_container_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *FetchAttestationRequest) GetReportData() []byte {
|
||||
if x != nil {
|
||||
return x.ReportData
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type FetchAttestationReply struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Attestation []byte `protobuf:"bytes,1,opt,name=attestation,proto3" json:"attestation,omitempty"`
|
||||
}
|
||||
|
||||
func (x *FetchAttestationReply) Reset() {
|
||||
*x = FetchAttestationReply{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_protobuf_attestation_container_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *FetchAttestationReply) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*FetchAttestationReply) ProtoMessage() {}
|
||||
|
||||
func (x *FetchAttestationReply) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_protobuf_attestation_container_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use FetchAttestationReply.ProtoReflect.Descriptor instead.
|
||||
func (*FetchAttestationReply) Descriptor() ([]byte, []int) {
|
||||
return file_protobuf_attestation_container_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *FetchAttestationReply) GetAttestation() []byte {
|
||||
if x != nil {
|
||||
return x.Attestation
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var File_protobuf_attestation_container_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_protobuf_attestation_container_proto_rawDesc = []byte{
|
||||
0x0a, 0x24, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2f, 0x61, 0x74, 0x74, 0x65, 0x73,
|
||||
0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72,
|
||||
0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x22, 0x3a, 0x0a,
|
||||
0x17, 0x46, 0x65, 0x74, 0x63, 0x68, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x72, 0x65, 0x70, 0x6f,
|
||||
0x72, 0x74, 0x5f, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0a, 0x72,
|
||||
0x65, 0x70, 0x6f, 0x72, 0x74, 0x44, 0x61, 0x74, 0x61, 0x22, 0x39, 0x0a, 0x15, 0x46, 0x65, 0x74,
|
||||
0x63, 0x68, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70,
|
||||
0x6c, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x32, 0x8a, 0x01, 0x0a, 0x14, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61,
|
||||
0x74, 0x69, 0x6f, 0x6e, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x72, 0x0a,
|
||||
0x10, 0x46, 0x65, 0x74, 0x63, 0x68, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x12, 0x2e, 0x2e, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x2c, 0x2e, 0x61, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x5f,
|
||||
0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x2e, 0x46, 0x65, 0x74, 0x63, 0x68, 0x41,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22,
|
||||
0x00, 0x42, 0x2a, 0x5a, 0x28, 0x6d, 0x69, 0x63, 0x72, 0x6f, 0x73, 0x6f, 0x66, 0x74, 0x2f, 0x61,
|
||||
0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2d, 0x63, 0x6f, 0x6e, 0x74, 0x61,
|
||||
0x69, 0x6e, 0x65, 0x72, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x62, 0x06, 0x70,
|
||||
0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_protobuf_attestation_container_proto_rawDescOnce sync.Once
|
||||
file_protobuf_attestation_container_proto_rawDescData = file_protobuf_attestation_container_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_protobuf_attestation_container_proto_rawDescGZIP() []byte {
|
||||
file_protobuf_attestation_container_proto_rawDescOnce.Do(func() {
|
||||
file_protobuf_attestation_container_proto_rawDescData = protoimpl.X.CompressGZIP(file_protobuf_attestation_container_proto_rawDescData)
|
||||
})
|
||||
return file_protobuf_attestation_container_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_protobuf_attestation_container_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_protobuf_attestation_container_proto_goTypes = []interface{}{
|
||||
(*FetchAttestationRequest)(nil), // 0: attestation_container.FetchAttestationRequest
|
||||
(*FetchAttestationReply)(nil), // 1: attestation_container.FetchAttestationReply
|
||||
}
|
||||
var file_protobuf_attestation_container_proto_depIdxs = []int32{
|
||||
0, // 0: attestation_container.AttestationContainer.FetchAttestation:input_type -> attestation_container.FetchAttestationRequest
|
||||
1, // 1: attestation_container.AttestationContainer.FetchAttestation:output_type -> attestation_container.FetchAttestationReply
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_protobuf_attestation_container_proto_init() }
|
||||
func file_protobuf_attestation_container_proto_init() {
|
||||
if File_protobuf_attestation_container_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_protobuf_attestation_container_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FetchAttestationRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_protobuf_attestation_container_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*FetchAttestationReply); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_protobuf_attestation_container_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_protobuf_attestation_container_proto_goTypes,
|
||||
DependencyIndexes: file_protobuf_attestation_container_proto_depIdxs,
|
||||
MessageInfos: file_protobuf_attestation_container_proto_msgTypes,
|
||||
}.Build()
|
||||
File_protobuf_attestation_container_proto = out.File
|
||||
file_protobuf_attestation_container_proto_rawDesc = nil
|
||||
file_protobuf_attestation_container_proto_goTypes = nil
|
||||
file_protobuf_attestation_container_proto_depIdxs = nil
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
syntax = "proto3";
|
||||
|
||||
option go_package = "microsoft/attestation-container/protobuf";
|
||||
|
||||
package attestation_container;
|
||||
|
||||
// attestation_container service definition.
|
||||
service AttestationContainer {
|
||||
// Fetchs and returns attestation report and collateral.
|
||||
// In future it returns Certificate Revocation List (CRL) as well.
|
||||
rpc FetchAttestation (FetchAttestationRequest) returns (FetchAttestationReply) {}
|
||||
}
|
||||
|
||||
message FetchAttestationRequest {
|
||||
bytes report_data = 1;
|
||||
}
|
||||
|
||||
message FetchAttestationReply {
|
||||
bytes attestation = 1;
|
||||
}
|
|
@ -0,0 +1,109 @@
|
|||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.12.4
|
||||
// source: protobuf/attestation-container.proto
|
||||
|
||||
package protobuf
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// AttestationContainerClient is the client API for AttestationContainer service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type AttestationContainerClient interface {
|
||||
// Fetchs and returns attestation report and collateral.
|
||||
// In future it returns Certificate Revocation List (CRL) as well.
|
||||
FetchAttestation(ctx context.Context, in *FetchAttestationRequest, opts ...grpc.CallOption) (*FetchAttestationReply, error)
|
||||
}
|
||||
|
||||
type attestationContainerClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewAttestationContainerClient(cc grpc.ClientConnInterface) AttestationContainerClient {
|
||||
return &attestationContainerClient{cc}
|
||||
}
|
||||
|
||||
func (c *attestationContainerClient) FetchAttestation(ctx context.Context, in *FetchAttestationRequest, opts ...grpc.CallOption) (*FetchAttestationReply, error) {
|
||||
out := new(FetchAttestationReply)
|
||||
err := c.cc.Invoke(ctx, "/attestation_container.AttestationContainer/FetchAttestation", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AttestationContainerServer is the server API for AttestationContainer service.
|
||||
// All implementations must embed UnimplementedAttestationContainerServer
|
||||
// for forward compatibility
|
||||
type AttestationContainerServer interface {
|
||||
// Fetchs and returns attestation report and collateral.
|
||||
// In future it returns Certificate Revocation List (CRL) as well.
|
||||
FetchAttestation(context.Context, *FetchAttestationRequest) (*FetchAttestationReply, error)
|
||||
mustEmbedUnimplementedAttestationContainerServer()
|
||||
}
|
||||
|
||||
// UnimplementedAttestationContainerServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedAttestationContainerServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedAttestationContainerServer) FetchAttestation(context.Context, *FetchAttestationRequest) (*FetchAttestationReply, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method FetchAttestation not implemented")
|
||||
}
|
||||
func (UnimplementedAttestationContainerServer) mustEmbedUnimplementedAttestationContainerServer() {}
|
||||
|
||||
// UnsafeAttestationContainerServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to AttestationContainerServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeAttestationContainerServer interface {
|
||||
mustEmbedUnimplementedAttestationContainerServer()
|
||||
}
|
||||
|
||||
func RegisterAttestationContainerServer(s grpc.ServiceRegistrar, srv AttestationContainerServer) {
|
||||
s.RegisterService(&AttestationContainer_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _AttestationContainer_FetchAttestation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(FetchAttestationRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AttestationContainerServer).FetchAttestation(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/attestation_container.AttestationContainer/FetchAttestation",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AttestationContainerServer).FetchAttestation(ctx, req.(*FetchAttestationRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// AttestationContainer_ServiceDesc is the grpc.ServiceDesc for AttestationContainer service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var AttestationContainer_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "attestation_container.AttestationContainer",
|
||||
HandlerType: (*AttestationContainerServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "FetchAttestation",
|
||||
Handler: _AttestationContainer_FetchAttestation_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "protobuf/attestation-container.proto",
|
||||
}
|
|
@ -11,6 +11,7 @@ from azure.mgmt.resource.resources.models import (
|
|||
Deployment,
|
||||
DeploymentProperties,
|
||||
DeploymentMode,
|
||||
DeploymentPropertiesExtended,
|
||||
)
|
||||
from azure.mgmt.containerinstance import ContainerInstanceManagementClient
|
||||
|
||||
|
@ -90,6 +91,13 @@ def make_aci_deployment(parser: ArgumentParser) -> Deployment:
|
|||
type=lambda comma_sep_str: comma_sep_str.split(","),
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--attestation-container-e2e",
|
||||
help="Deploy attestation container for its E2E test if this flag is true. Default=False",
|
||||
default=False,
|
||||
type=bool,
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--aci-storage-account-key",
|
||||
help="The storage account key used to authorise access to the file share",
|
||||
|
@ -98,87 +106,144 @@ def make_aci_deployment(parser: ArgumentParser) -> Deployment:
|
|||
|
||||
args = parser.parse_args()
|
||||
|
||||
return Deployment(
|
||||
properties=DeploymentProperties(
|
||||
mode=DeploymentMode.INCREMENTAL,
|
||||
parameters={},
|
||||
template={
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {},
|
||||
"variables": {},
|
||||
"resources": [
|
||||
{
|
||||
"type": "Microsoft.ContainerInstance/containerGroups",
|
||||
"apiVersion": "2022-04-01-preview",
|
||||
"name": f"{args.deployment_name}-{i}",
|
||||
"location": "eastus2euap",
|
||||
"properties": {
|
||||
"sku": "Standard",
|
||||
"confidentialComputeProperties": {
|
||||
"isolationType": "SevSnp",
|
||||
"ccePolicy": "cGFja2FnZSBwb2xpY3kKCmFwaV9zdm4gOj0gIjAuMTAuMCIKCm1vdW50X2RldmljZSA6PSB7ImFsbG93ZWQiOiB0cnVlfQptb3VudF9vdmVybGF5IDo9IHsiYWxsb3dlZCI6IHRydWV9CmNyZWF0ZV9jb250YWluZXIgOj0geyJhbGxvd2VkIjogdHJ1ZSwgImFsbG93X3N0ZGlvX2FjY2VzcyI6IHRydWV9CnVubW91bnRfZGV2aWNlIDo9IHsiYWxsb3dlZCI6IHRydWV9CnVubW91bnRfb3ZlcmxheSA6PSB7ImFsbG93ZWQiOiB0cnVlfQpleGVjX2luX2NvbnRhaW5lciA6PSB7ImFsbG93ZWQiOiB0cnVlfQpleGVjX2V4dGVybmFsIDo9IHsiYWxsb3dlZCI6IHRydWUsICJhbGxvd19zdGRpb19hY2Nlc3MiOiB0cnVlfQpzaHV0ZG93bl9jb250YWluZXIgOj0geyJhbGxvd2VkIjogdHJ1ZX0Kc2lnbmFsX2NvbnRhaW5lcl9wcm9jZXNzIDo9IHsiYWxsb3dlZCI6IHRydWV9CnBsYW45X21vdW50IDo9IHsiYWxsb3dlZCI6IHRydWV9CnBsYW45X3VubW91bnQgOj0geyJhbGxvd2VkIjogdHJ1ZX0KZ2V0X3Byb3BlcnRpZXMgOj0geyJhbGxvd2VkIjogdHJ1ZX0KZHVtcF9zdGFja3MgOj0geyJhbGxvd2VkIjogdHJ1ZX0KcnVudGltZV9sb2dnaW5nIDo9IHsiYWxsb3dlZCI6IHRydWV9CmxvYWRfZnJhZ21lbnQgOj0geyJhbGxvd2VkIjogdHJ1ZX0Kc2NyYXRjaF9tb3VudCA6PSB7ImFsbG93ZWQiOiB0cnVlfQpzY3JhdGNoX3VubW91bnQgOj0geyJhbGxvd2VkIjogdHJ1ZX0K",
|
||||
},
|
||||
"containers": [
|
||||
{
|
||||
"name": f"{args.deployment_name}-{i}",
|
||||
"properties": {
|
||||
"image": args.aci_image,
|
||||
"command": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
" && ".join(
|
||||
[
|
||||
*STARTUP_COMMANDS[args.aci_type](
|
||||
args,
|
||||
i,
|
||||
),
|
||||
"tail -f /dev/null",
|
||||
]
|
||||
common_resource_attributes = {
|
||||
"type": "Microsoft.ContainerInstance/containerGroups",
|
||||
"apiVersion": "2022-04-01-preview",
|
||||
"location": "eastus2euap",
|
||||
}
|
||||
|
||||
common_resource_properties = {
|
||||
"sku": "Standard",
|
||||
"confidentialComputeProperties": {
|
||||
"isolationType": "SevSnp",
|
||||
"ccePolicy": "cGFja2FnZSBwb2xpY3kKCmFwaV9zdm4gOj0gIjAuMTAuMCIKCm1vdW50X2RldmljZSA6PSB7ImFsbG93ZWQiOiB0cnVlfQptb3VudF9vdmVybGF5IDo9IHsiYWxsb3dlZCI6IHRydWV9CmNyZWF0ZV9jb250YWluZXIgOj0geyJhbGxvd2VkIjogdHJ1ZSwgImFsbG93X3N0ZGlvX2FjY2VzcyI6IHRydWV9CnVubW91bnRfZGV2aWNlIDo9IHsiYWxsb3dlZCI6IHRydWV9CnVubW91bnRfb3ZlcmxheSA6PSB7ImFsbG93ZWQiOiB0cnVlfQpleGVjX2luX2NvbnRhaW5lciA6PSB7ImFsbG93ZWQiOiB0cnVlfQpleGVjX2V4dGVybmFsIDo9IHsiYWxsb3dlZCI6IHRydWUsICJhbGxvd19zdGRpb19hY2Nlc3MiOiB0cnVlfQpzaHV0ZG93bl9jb250YWluZXIgOj0geyJhbGxvd2VkIjogdHJ1ZX0Kc2lnbmFsX2NvbnRhaW5lcl9wcm9jZXNzIDo9IHsiYWxsb3dlZCI6IHRydWV9CnBsYW45X21vdW50IDo9IHsiYWxsb3dlZCI6IHRydWV9CnBsYW45X3VubW91bnQgOj0geyJhbGxvd2VkIjogdHJ1ZX0KZ2V0X3Byb3BlcnRpZXMgOj0geyJhbGxvd2VkIjogdHJ1ZX0KZHVtcF9zdGFja3MgOj0geyJhbGxvd2VkIjogdHJ1ZX0KcnVudGltZV9sb2dnaW5nIDo9IHsiYWxsb3dlZCI6IHRydWV9CmxvYWRfZnJhZ21lbnQgOj0geyJhbGxvd2VkIjogdHJ1ZX0Kc2NyYXRjaF9tb3VudCA6PSB7ImFsbG93ZWQiOiB0cnVlfQpzY3JhdGNoX3VubW91bnQgOj0geyJhbGxvd2VkIjogdHJ1ZX0K",
|
||||
},
|
||||
"initContainers": [],
|
||||
"restartPolicy": "Never",
|
||||
"osType": "Linux",
|
||||
}
|
||||
|
||||
# Mount external volume only when account key is provided
|
||||
volume_mounts = []
|
||||
if args.aci_storage_account_key is not None:
|
||||
common_resource_properties["volumes"] = [
|
||||
{
|
||||
"name": "ccfcivolume",
|
||||
"azureFile": {
|
||||
"shareName": "ccfcishare",
|
||||
"storageAccountName": "ccfcistorage",
|
||||
"storageAccountKey": args.aci_storage_account_key,
|
||||
},
|
||||
}
|
||||
]
|
||||
volume_mounts = [
|
||||
{
|
||||
"name": "ccfcivolume",
|
||||
"mountPath": "/ccfci",
|
||||
}
|
||||
]
|
||||
|
||||
template = {
|
||||
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
|
||||
"contentVersion": "1.0.0.0",
|
||||
"parameters": {},
|
||||
"variables": {},
|
||||
"resources": [
|
||||
{
|
||||
**common_resource_attributes,
|
||||
"name": f"{args.deployment_name}-{i}",
|
||||
"properties": {
|
||||
**common_resource_properties,
|
||||
"containers": [
|
||||
{
|
||||
"name": f"{args.deployment_name}-{i}",
|
||||
"properties": {
|
||||
"image": args.aci_image,
|
||||
"command": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
" && ".join(
|
||||
[
|
||||
*STARTUP_COMMANDS[args.aci_type](
|
||||
args,
|
||||
i,
|
||||
),
|
||||
],
|
||||
"ports": [
|
||||
{"protocol": "TCP", "port": 8000},
|
||||
{"protocol": "TCP", "port": 22},
|
||||
],
|
||||
"environmentVariables": [],
|
||||
"resources": {
|
||||
"requests": {"memoryInGB": 16, "cpu": 4}
|
||||
},
|
||||
"volumeMounts": [
|
||||
{
|
||||
"name": "ccfcivolume",
|
||||
"mountPath": "/ccfci",
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
],
|
||||
"initContainers": [],
|
||||
"restartPolicy": "Never",
|
||||
"ipAddress": {
|
||||
"tail -f /dev/null",
|
||||
]
|
||||
),
|
||||
],
|
||||
"ports": [
|
||||
{"protocol": "TCP", "port": 8000},
|
||||
{"protocol": "TCP", "port": 22},
|
||||
],
|
||||
"type": "Public",
|
||||
"environmentVariables": [],
|
||||
"resources": {"requests": {"memoryInGB": 16, "cpu": 4}},
|
||||
"volumeMounts": volume_mounts,
|
||||
},
|
||||
"osType": "Linux",
|
||||
"volumes": [
|
||||
{
|
||||
"name": "ccfcivolume",
|
||||
"azureFile": {
|
||||
"shareName": "ccfcishare",
|
||||
"storageAccountName": "ccfcistorage",
|
||||
"storageAccountKey": args.aci_storage_account_key,
|
||||
},
|
||||
}
|
||||
],
|
||||
},
|
||||
}
|
||||
for i in range(args.count)
|
||||
],
|
||||
},
|
||||
}
|
||||
],
|
||||
"ipAddress": {
|
||||
"ports": [
|
||||
{"protocol": "TCP", "port": 8000},
|
||||
{"protocol": "TCP", "port": 22},
|
||||
],
|
||||
"type": "Public",
|
||||
},
|
||||
},
|
||||
}
|
||||
for i in range(args.count)
|
||||
],
|
||||
}
|
||||
|
||||
if args.attestation_container_e2e:
|
||||
# NOTE: It currently exposes the attestation-container's port (50051) to outside of the container group for e2e testing purpose,
|
||||
# but it shouldn't be done in actual CCF application.
|
||||
template["resources"].append(
|
||||
{
|
||||
**common_resource_attributes,
|
||||
"name": f"{args.deployment_name}-business-logic",
|
||||
"properties": {
|
||||
**common_resource_properties,
|
||||
"containers": [
|
||||
{
|
||||
"name": f"{args.deployment_name}-attestation-container",
|
||||
"properties": {
|
||||
"image": f"attestationcontainerregistry.azurecr.io/attestation-container:{args.deployment_name}",
|
||||
"command": [
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
" && ".join(
|
||||
[
|
||||
*STARTUP_COMMANDS[args.aci_type](
|
||||
args,
|
||||
None,
|
||||
),
|
||||
"app",
|
||||
]
|
||||
),
|
||||
],
|
||||
"ports": [
|
||||
{"protocol": "TCP", "port": 22},
|
||||
{"protocol": "TCP", "port": 50051},
|
||||
],
|
||||
"environmentVariables": [],
|
||||
"resources": {"requests": {"memoryInGB": 16, "cpu": 4}},
|
||||
},
|
||||
}
|
||||
],
|
||||
"ipAddress": {
|
||||
"ports": [
|
||||
{"protocol": "TCP", "port": 22},
|
||||
{"protocol": "TCP", "port": 50051},
|
||||
],
|
||||
"type": "Public",
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
return Deployment(
|
||||
properties=DeploymentProperties(
|
||||
mode=DeploymentMode.INCREMENTAL, parameters={}, template=template
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -196,16 +261,26 @@ def remove_aci_deployment(args: Namespace, deployment: Deployment):
|
|||
).wait()
|
||||
|
||||
|
||||
def check_aci_deployment(args: Namespace, deployment: Deployment) -> str:
|
||||
def check_aci_deployment(
|
||||
args: Namespace, deployment: DeploymentPropertiesExtended
|
||||
) -> str:
|
||||
"""
|
||||
Outputs the list of container group deployed to stdout.
|
||||
The format of each line is `<container group name> <IP address>`.
|
||||
|
||||
example output:
|
||||
container_group_a 10.10.10.10
|
||||
container_group_b 10.10.10.11
|
||||
"""
|
||||
|
||||
container_client = ContainerInstanceManagementClient(
|
||||
DefaultAzureCredential(), args.subscription_id
|
||||
)
|
||||
|
||||
for resource in deployment.properties.output_resources:
|
||||
container_name = resource.id.split("/")[-1]
|
||||
container_group_name = resource.id.split("/")[-1]
|
||||
container_group = container_client.container_groups.get(
|
||||
args.resource_group, container_name
|
||||
args.resource_group, container_group_name
|
||||
)
|
||||
|
||||
# Check that container commands have been completed
|
||||
|
@ -228,7 +303,7 @@ def check_aci_deployment(args: Namespace, deployment: Deployment) -> str:
|
|||
)
|
||||
== b"test\n"
|
||||
)
|
||||
print(container_group.ip_address.ip)
|
||||
print(container_group_name, container_group.ip_address.ip)
|
||||
break
|
||||
except Exception:
|
||||
time.sleep(5)
|
||||
|
|
|
@ -125,3 +125,43 @@ endgroup
|
|||
group "Python types"
|
||||
git ls-files python/ | grep -e '\.py$' | xargs mypy
|
||||
endgroup
|
||||
|
||||
group "Go dependencies"
|
||||
GO_VERSION="1.19"
|
||||
if command -v go &> /dev/null
|
||||
then
|
||||
# go is found
|
||||
if ! go version | grep go$GO_VERSION &> /dev/null
|
||||
then
|
||||
echo "Wrong version of go is installed. Please make sure version $GO_VERSION.x is installed."
|
||||
echo -n "Current install version: "
|
||||
go version
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
# go is not found
|
||||
# Install the latest bugfix version of GO_VERSION
|
||||
# https://github.com/golang/go/issues/36898
|
||||
install_version=$(curl -sL 'https://go.dev/dl/?mode=json&include=all' | jq -r '.[].version' | grep -m 1 go$GO_VERSION)
|
||||
tar_filename=$install_version.linux-amd64.tar.gz
|
||||
curl -sLO "https://go.dev/dl/$tar_filename"
|
||||
function clean_up_tar {
|
||||
rm "$tar_filename"
|
||||
}
|
||||
trap clean_up_tar EXIT
|
||||
tar -C /usr/local -xzf "$tar_filename"
|
||||
# shellcheck disable=SC2016,SC1090
|
||||
echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc && source ~/.bashrc
|
||||
fi
|
||||
|
||||
group "Go format"
|
||||
if [ $FIX -ne 0 ]; then
|
||||
git ls-files attestation-container/ | grep -e '\.go$' | xargs gofmt -w
|
||||
else
|
||||
GOFMT_RES=$(git ls-files attestation-container/ | grep -e '\.go$' | xargs gofmt -d)
|
||||
if [ "$GOFMT_RES" != "" ];
|
||||
then
|
||||
echo "Format of go codes is broken"
|
||||
echo "$GOFMT_RES"
|
||||
fi
|
||||
fi
|
||||
|
|
Загрузка…
Ссылка в новой задаче