Skip to content

Commit 1be22d6

Browse files
authored
Merge pull request #45 from ibm-hyper-protect/cleue-add-attestation-sample
feat: attestation sample
2 parents 476f060 + bb98c2e commit 1be22d6

File tree

5 files changed

+410
-0
lines changed

5 files changed

+410
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
## Attestation Sample
2+
3+
This sample deploys the [attestation](https://hub.docker.com/_/attestation) example as a [IBM Cloud Hyper Protect Virtual Server for IBM Cloud VPC](https://cloud.ibm.com/docs/vpc?topic=vpc-about-se).
4+
5+
### Prerequisite
6+
7+
Prepare your environment according to [these steps](../README.md)
8+
9+
### Settings
10+
11+
Use one of the following options to set you settings:
12+
13+
#### Template file
14+
15+
1. `cp my-settings.auto.tfvars-template my-settings.auto.tfvars`
16+
2. Fill the values in `my-settings.auto.tfvars`
17+
18+
#### Environment variables
19+
20+
Set the following environment variables:
21+
22+
```text
23+
IC_API_KEY=
24+
TF_VAR_zone=
25+
TF_VAR_region=
26+
TF_VAR_logdna_ingestion_key=
27+
TF_VAR_logdna_ingestion_hostname=
28+
```
29+
30+
### Run the Example
31+
32+
Initialize terraform:
33+
34+
```bash
35+
terraform init
36+
```
37+
38+
Deploy the example:
39+
40+
```bash
41+
terraform apply
42+
```
43+
44+
This will create a sample virtual server instance. Monitor your logDNA instance for
45+
46+
```text
47+
hpcr-sample-attestation-vsi compose-helloworld-1 info
48+
hpcr-sample-attestation-vsi compose-helloworld-1 info Hello from Docker!
49+
hpcr-sample-attestation-vsi compose-helloworld-1 info This message shows that your installation appears to be working correctly.
50+
hpcr-sample-attestation-vsi compose-helloworld-1 info
51+
hpcr-sample-attestation-vsi compose-helloworld-1 info To generate this message, Docker took the following steps:
52+
hpcr-sample-attestation-vsi compose-helloworld-1 info 1. The Docker client contacted the Docker daemon.
53+
hpcr-sample-attestation-vsi compose-helloworld-1 info 2. The Docker daemon pulled the "attestation" image from the Docker Hub.
54+
hpcr-sample-attestation-vsi compose-helloworld-1 info (s390x)
55+
hpcr-sample-attestation-vsi compose-helloworld-1 info 3. The Docker daemon created a new container from that image which runs the
56+
hpcr-sample-attestation-vsi compose-helloworld-1 info executable that produces the output you are currently reading.
57+
hpcr-sample-attestation-vsi compose-helloworld-1 info 4. The Docker daemon streamed that output to the Docker client, which sent it
58+
hpcr-sample-attestation-vsi compose-helloworld-1 info to your terminal.
59+
hpcr-sample-attestation-vsi compose-helloworld-1 info
60+
hpcr-sample-attestation-vsi compose-helloworld-1 info To try something more ambitious, you can run an Ubuntu container with:
61+
hpcr-sample-attestation-vsi compose-helloworld-1 info $ docker run -it ubuntu bash
62+
hpcr-sample-attestation-vsi compose-helloworld-1 info
63+
hpcr-sample-attestation-vsi compose-helloworld-1 info Share images, automate workflows, and more with a free Docker ID:
64+
hpcr-sample-attestation-vsi compose-helloworld-1 info https://hub.docker.com/
65+
hpcr-sample-attestation-vsi compose-helloworld-1 info
66+
hpcr-sample-attestation-vsi compose-helloworld-1 info For more examples and ideas, visit:
67+
hpcr-sample-attestation-vsi compose-helloworld-1 info https://docs.docker.com/get-started/
68+
hpcr-sample-attestation-vsi compose-helloworld-1 info
69+
```
70+
71+
Destroy the created resources:
72+
73+
```bash
74+
terraform destroy
75+
```
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
version: '3.0'
2+
services:
3+
busybox:
4+
image: registry.access.redhat.com/ubi8/ubi@sha256:e721f98a49e731f0bd64f8e89c229e1dbb38c46265d92849b3e0bedaf5f81139
5+
command: |
6+
curl -v -X "PUT" "${S3_URL}" -H "Authorization: ${AUTHORIZATION}" -H "Content-Type: text/plain" -d @/var/hyperprotect/se-checksums.txt.enc
7+
volumes:
8+
- /var/hyperprotect/:/var/hyperprotect/:ro
9+
environment:
10+
- AUTHORIZATION
11+
- S3_URL
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
ibmcloud_api_key="Your IBM Cloud API key"
2+
region="ca-tor" # Region to deploy to. Options include eu-gb, jp-tok, br-sao, ca-tor, us-east
3+
zone="2" # Zone within region to create the HPVS into.
4+
logdna_ingestion_key="Your LogDNA ingestion key" # You can find this in "Linux/ubuntu" section of `Logging sources` tab of "IBM Log Analysis" instance in [cloud.ibm.com](https://cloud.ibm.com)
5+
logdna_ingestion_hostname="rsyslog endpoint of IBM Log Analysis instance"
6+
# Example: "syslog-a.<log_region>.logging.cloud.ibm.com". Where <log_region> is
7+
# the region on which IBM Log Analysis is deployed
8+
# Any other variable you want to set (see variables.tf)
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
terraform {
2+
required_providers {
3+
hpcr = {
4+
source = "ibm-hyper-protect/hpcr"
5+
version = ">= 0.1.6"
6+
}
7+
8+
tls = {
9+
source = "hashicorp/tls"
10+
version = ">= 4.0.1"
11+
}
12+
13+
random = {
14+
source = "hashicorp/random"
15+
version = ">= 3.4.3"
16+
}
17+
18+
time = {
19+
source = "hashicorp/time"
20+
version = ">= 0.9.1"
21+
}
22+
23+
ibm = {
24+
source = "IBM-Cloud/ibm"
25+
version = ">= 1.37.1"
26+
}
27+
}
28+
}
29+
30+
# make sure to target the correct region and zone
31+
provider "ibm" {
32+
region = var.region
33+
zone = "${var.region}-${var.zone}"
34+
}
35+
36+
resource "random_uuid" "attestation_uuid" {
37+
}
38+
39+
locals {
40+
# some reusable tags that identify the resources created by his sample
41+
tags = ["hpcr", "sample", var.prefix]
42+
}
43+
44+
# the VPC
45+
resource "ibm_is_vpc" "attestation_vpc" {
46+
name = format("%s-vpc", var.prefix)
47+
tags = local.tags
48+
}
49+
50+
# locate the COS instance
51+
data "ibm_resource_instance" "attestation_cos_instance" {
52+
name = "secure-execution"
53+
location = "global"
54+
service = "cloud-object-storage"
55+
}
56+
57+
# create a bucket we use to upload the attestation record
58+
resource "ibm_cos_bucket" "attestation_cos_bucket" {
59+
resource_instance_id = data.ibm_resource_instance.attestation_cos_instance.id
60+
bucket_name = format("%s-bucket", var.prefix)
61+
region_location = var.region
62+
storage_class = "standard"
63+
}
64+
65+
# get the authentication token we use to upload the attestation record
66+
data "ibm_iam_auth_token" "attestation_auth_token" {
67+
}
68+
69+
# the security group
70+
resource "ibm_is_security_group" "attestation_security_group" {
71+
name = format("%s-security-group", var.prefix)
72+
vpc = ibm_is_vpc.attestation_vpc.id
73+
tags = local.tags
74+
}
75+
76+
# rule that allows the VSI to make outbound connections, this is required
77+
# to connect to the logDNA instance as well as to docker to pull the image
78+
resource "ibm_is_security_group_rule" "attestation_outbound" {
79+
group = ibm_is_security_group.attestation_security_group.id
80+
direction = "outbound"
81+
remote = "0.0.0.0/0"
82+
}
83+
84+
# the subnet
85+
resource "ibm_is_subnet" "attestation_subnet" {
86+
name = format("%s-subnet", var.prefix)
87+
vpc = ibm_is_vpc.attestation_vpc.id
88+
total_ipv4_address_count = 256
89+
zone = "${var.region}-${var.zone}"
90+
tags = local.tags
91+
}
92+
93+
# we use a gateway to allow the VSI to connect to the internet to logDNA
94+
# and docker. Without a gateway the VSI would need a floating IP. Without
95+
# either the VSI will not be able to connect to the internet despite
96+
# an outbound rule
97+
resource "ibm_is_public_gateway" "attestation_gateway" {
98+
name = format("%s-gateway", var.prefix)
99+
vpc = ibm_is_vpc.attestation_vpc.id
100+
zone = "${var.region}-${var.zone}"
101+
tags = local.tags
102+
}
103+
104+
# attach the gateway to the subnet
105+
resource "ibm_is_subnet_public_gateway_attachment" "attestation_gateway_attachment" {
106+
subnet = ibm_is_subnet.attestation_subnet.id
107+
public_gateway = ibm_is_public_gateway.attestation_gateway.id
108+
}
109+
110+
# archive of the folder containing docker-compose file. This folder could create additional resources such as files
111+
# to be mounted into containers, environment files etc. This is why all of these files get bundled in a tgz file (base64 encoded)
112+
resource "hpcr_tgz" "contract" {
113+
folder = "compose"
114+
}
115+
116+
locals {
117+
# URL to the attestation object
118+
attestationKey = format("%s.enc", random_uuid.attestation_uuid.result)
119+
attestationURL = format("https://%s/%s/%s", ibm_cos_bucket.attestation_cos_bucket.s3_endpoint_public, urlencode(ibm_cos_bucket.attestation_cos_bucket.bucket_name), urlencode(local.attestationKey))
120+
# contract in clear text
121+
contract = yamlencode({
122+
"env" : {
123+
"type" : "env",
124+
"logging" : {
125+
"logDNA" : {
126+
"ingestionKey" : var.logdna_ingestion_key,
127+
"hostname" : var.logdna_ingestion_hostname,
128+
}
129+
},
130+
"env" : {
131+
"AUTHORIZATION" : data.ibm_iam_auth_token.attestation_auth_token.iam_access_token,
132+
"S3_URL" : local.attestationURL
133+
}
134+
},
135+
"workload" : {
136+
"type" : "workload",
137+
"compose" : {
138+
"archive" : hpcr_tgz.contract.rendered
139+
}
140+
},
141+
"attestationPublicKey" : tls_private_key.attestation_enc_rsa_key.public_key_pem
142+
})
143+
}
144+
145+
# create a key pair for the purpose of encrypting the attestation record
146+
resource "tls_private_key" "attestation_enc_rsa_key" {
147+
algorithm = "RSA"
148+
rsa_bits = 4096
149+
}
150+
151+
# create a random key pair, because for formal reasons we need to pass an SSH key into the VSI. It will not be used, that's why
152+
# it can be random
153+
resource "tls_private_key" "attestation_rsa_key" {
154+
algorithm = "RSA"
155+
rsa_bits = 4096
156+
}
157+
158+
# we only need this because VPC expects this
159+
resource "ibm_is_ssh_key" "attestation_sshkey" {
160+
name = format("%s-key", var.prefix)
161+
public_key = tls_private_key.attestation_rsa_key.public_key_openssh
162+
tags = local.tags
163+
}
164+
165+
# locate all public image
166+
data "ibm_is_images" "hyper_protect_images" {
167+
visibility = "public"
168+
status = "available"
169+
}
170+
171+
# locate the latest hyper protect image
172+
data "hpcr_image" "hyper_protect_image" {
173+
images = jsonencode(data.ibm_is_images.hyper_protect_images.images)
174+
}
175+
176+
# In this step we encrypt the fields of the contract and sign the env and workload field. The certificate to execute the
177+
# encryption it built into the provider and matches the latest HPCR image. If required it can be overridden.
178+
# We use a temporary, random keypair to execute the signature. This could also be overriden.
179+
resource "hpcr_contract_encrypted" "contract" {
180+
contract = local.contract
181+
}
182+
183+
# construct the VSI
184+
resource "ibm_is_instance" "attestation_vsi" {
185+
name = format("%s-vsi", var.prefix)
186+
image = data.hpcr_image.hyper_protect_image.image
187+
profile = var.profile
188+
keys = [ibm_is_ssh_key.attestation_sshkey.id]
189+
vpc = ibm_is_vpc.attestation_vpc.id
190+
tags = local.tags
191+
zone = "${var.region}-${var.zone}"
192+
193+
# the user data field carries the encrypted contract, so all information visible at the hypervisor layer is encrypted
194+
user_data = hpcr_contract_encrypted.contract.rendered
195+
196+
primary_network_interface {
197+
name = "eth0"
198+
subnet = ibm_is_subnet.attestation_subnet.id
199+
security_groups = [ibm_is_security_group.attestation_security_group.id]
200+
}
201+
202+
}
203+
204+
205+
# huhh, this is not nice
206+
resource "time_sleep" "wait_for_attestation" {
207+
depends_on = [
208+
ibm_is_instance.attestation_vsi
209+
]
210+
211+
create_duration = "45s"
212+
}
213+
214+
data "ibm_cos_bucket_object" "attestation_record" {
215+
bucket_crn = ibm_cos_bucket.attestation_cos_bucket.crn
216+
bucket_location = ibm_cos_bucket.attestation_cos_bucket.region_location
217+
key = local.attestationKey
218+
endpoint_type = "public"
219+
depends_on = [
220+
time_sleep.wait_for_attestation
221+
]
222+
}
223+
224+
data "hpcr_attestation" "attestation_decoded" {
225+
attestation = data.ibm_cos_bucket_object.attestation_record.body
226+
privkey = tls_private_key.attestation_enc_rsa_key.private_key_pem
227+
}
228+
229+
resource "local_file" "contract" {
230+
content = hpcr_contract_encrypted.contract.rendered
231+
filename = "${path.module}/build/contract.yml"
232+
}
233+
234+
resource "local_file" "attestation_pub_key" {
235+
content = tls_private_key.attestation_enc_rsa_key.public_key_pem
236+
filename = "${path.module}/build/attestation.pub"
237+
}
238+
239+
resource "local_file" "attestation_priv_key" {
240+
content = tls_private_key.attestation_enc_rsa_key.private_key_pem_pkcs8
241+
filename = "${path.module}/build/attestation"
242+
}
243+
244+
resource "local_file" "attestation_record" {
245+
content = jsonencode(data.hpcr_attestation.attestation_decoded.checksums)
246+
filename = "${path.module}/build/se-checksums.json"
247+
}

0 commit comments

Comments
 (0)